From 5ede5044c210162135c574061eab24ed3c2b2d7d Mon Sep 17 00:00:00 2001 From: Alex Heller Date: Sun, 30 Apr 2023 04:06:02 +0200 Subject: [PATCH 01/31] Fix: Made spacing between setting items more consistent --- style.css | 1202 +++++++++++++++++++++++++++-------------------------- 1 file changed, 605 insertions(+), 597 deletions(-) diff --git a/style.css b/style.css index 996ad15d2..a2f03ca7f 100644 --- a/style.css +++ b/style.css @@ -1,597 +1,605 @@ -:root, .dark{ --checkbox-label-gap: 0.25em 0.1em; --section-header-text-size: 12pt; --block-background-fill: transparent;} -.block.padded:not(.gradio-accordion) { padding: 0 !important; } -div.gradio-container{ max-width: unset !important; } -.hidden{ display: none; } -.compact{ background: transparent !important; padding: 0 !important; } -div.form{ border-width: 0; box-shadow: none; background: transparent; overflow: visible; gap: 0.5em; } -.block.gradio-dropdown, .block.gradio-slider, .block.gradio-checkbox, .block.gradio-textbox, .block.gradio-radio, .block.gradio-checkboxgroup, .block.gradio-number, .block.gradio-colorpicker { border-width: 0 !important; box-shadow: none !important;} -.gap.compact{ padding: 0; gap: 0.2em 0; } -div.compact{ gap: 1em; } -.gradio-dropdown label span:not(.has-info), .gradio-textbox label span:not(.has-info), .gradio-number label span:not(.has-info) { margin-bottom: 0; } -.gradio-dropdown ul.options{ z-index: 3000; min-width: fit-content; max-width: inherit; white-space: nowrap; } -.gradio-dropdown ul.options li.item { padding: 0.05em 0; } -.gradio-dropdown ul.options li.item:not(:has(.hide)) { background-color: var(--neutral-100); } -.dark .gradio-dropdown ul.options li.item:not(:has(.hide)) { background-color: var(--neutral-900); } -.gradio-dropdown div.wrap.wrap.wrap.wrap{ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); } -.gradio-dropdown:not(.multiselect) .wrap-inner.wrap-inner.wrap-inner{ flex-wrap: unset; } -.gradio-dropdown .single-select{ white-space: nowrap; overflow: hidden; } -.gradio-dropdown .token-remove.remove-all.remove-all{ display: none; } -.gradio-dropdown.multiselect .token-remove.remove-all.remove-all{ display: flex; } -.gradio-slider input[type="number"]{ width: 6em; } -.block.gradio-checkbox { margin: 0.75em 1.5em 0 0; } -.gradio-html div.wrap{ height: 100%; } -div.gradio-html.min{ min-height: 0; } -.block.gradio-gallery{ background: var(--input-background-fill); } -.gradio-container .prose a, .gradio-container .prose a:visited{ color: unset; text-decoration: none; } - -/* general styled components */ -.gradio-button.tool{ max-width: 2.2em; min-width: 2.2em !important; height: 2.4em; align-self: end; line-height: 1em; border-radius: 0.5em; } -.gradio-button.secondary-down{ background: var(--button-secondary-background-fill); color: var(--button-secondary-text-color); } -.gradio-button.secondary-down, .gradio-button.secondary-down:hover{ box-shadow: 1px 1px 1px rgba(0,0,0,0.25) inset, 0px 0px 3px rgba(0,0,0,0.15) inset; } -.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); - font-weight: var(--button-large-text-weight); - border: var(--button-border-width) solid var(--button-secondary-border-color); - background: var(--button-secondary-background-fill); - color: var(--button-secondary-text-color); - font-size: var(--button-large-text-size); - display: inline-flex; - justify-content: center; - align-items: center; - transition: var(--button-transition); - box-shadow: var(--button-shadow); - text-align: center; -} - -/* txt2img/img2img specific */ -.block.token-counter{ - position: absolute; - display: inline-block; - right: 1em; - min-width: 0 !important; - width: auto; - z-index: 100; - top: -0.75em; -} - -.block.token-counter span{ - background: var(--input-background-fill) !important; - box-shadow: 0 0 0.0 0.3em rgba(192,192,192,0.15), inset 0 0 0.6em rgba(192,192,192,0.075); - border: 2px solid rgba(192,192,192,0.4) !important; - border-radius: 0.4em; -} - -.block.token-counter.error span{ - box-shadow: 0 0 0.0 0.3em rgba(255,0,0,0.15), inset 0 0 0.6em rgba(255,0,0,0.075); - border: 2px solid rgba(255,0,0,0.4) !important; -} - -.block.token-counter div{ display: inline; } -.block.token-counter span{ padding: 0.1em 0.75em; } - -[id$=_subseed_show]{ - min-width: auto !important; - flex-grow: 0 !important; - display: flex; -} - -[id$=_subseed_show] label{ - margin-bottom: 0.5em; - align-self: end; -} - -.performance { - font-size: 0.85em; - color: #444; -} - -.performance p{ - display: inline-block; -} - -.performance .time { - margin-right: 0; -} - -#txt2img_generate, #img2img_generate { - min-height: 4.5em; -} - -@media screen and (min-width: 2500px) { - #txt2img_gallery, #img2img_gallery { - min-height: 768px; - } -} - -#txt2img_gallery img, #img2img_gallery img{ - object-fit: scale-down; -} -#txt2img_actions_column, #img2img_actions_column { - gap: 0.5em; -} -#txt2img_tools, #img2img_tools{ - gap: 0.4em; -} - -.interrogate-col{ - min-width: 0 !important; - max-width: fit-content; - gap: 0.5em; -} -.interrogate-col > button{ - flex: 1; -} - -.generate-box{ - position: relative; -} -.gradio-button.generate-box-skip, .gradio-button.generate-box-interrupt{ - position: absolute; - width: 50%; - height: 100%; - display: none; - background: #b4c0cc; -} -.gradio-button.generate-box-skip:hover, .gradio-button.generate-box-interrupt:hover{ - background: #c2cfdb; -} -.gradio-button.generate-box-interrupt{ - left: 0; - border-radius: 0.5rem 0 0 0.5rem; -} -.gradio-button.generate-box-skip{ - right: 0; - border-radius: 0 0.5rem 0.5rem 0; -} - -#txtimg_hr_finalres{ - min-height: 0 !important; - padding: .625rem .75rem; - margin-left: -0.75em -} - -#txtimg_hr_finalres .resolution{ - font-weight: bold; -} - -.inactive{ - opacity: 0.5; -} - -[id$=_column_batch]{ - min-width: min(13.5em, 100%) !important; -} - -div.dimensions-tools{ - min-width: 0 !important; - max-width: fit-content; - flex-direction: row; - align-content: center; -} - -div#extras_scale_to_tab div.form{ - flex-direction: row; -} - -#mode_img2img .gradio-image > div.fixed-height, #mode_img2img .gradio-image > div.fixed-height img{ - height: 480px !important; - max-height: 480px !important; - min-height: 480px !important; -} - -#img2img_sketch, #img2maskimg, #inpaint_sketch { - overflow: overlay !important; - resize: auto; - background: var(--panel-background-fill); - z-index: 5; -} - -.image-buttons button{ - min-width: auto; -} - -.infotext { - overflow-wrap: break-word; -} - -/* settings */ -#quicksettings { - width: fit-content; - align-items: end; -} - -#quicksettings > div, #quicksettings > fieldset{ - max-width: 24em; - min-width: 24em; - padding: 0; - border: none; - box-shadow: none; - background: none; -} - -#settings{ - display: block; -} - -#settings > div{ - border: none; - margin-left: 10em; -} - -#settings > div.tab-nav{ - float: left; - display: block; - margin-left: 0; - width: 10em; -} - -#settings > div.tab-nav button{ - display: block; - border: none; - text-align: left; - white-space: initial; -} - -#settings_result{ - height: 1.4em; - margin: 0 1.2em; -} - - -/* live preview */ -.progressDiv{ - position: relative; - height: 20px; - background: #b4c0cc; - border-radius: 3px !important; - margin-bottom: -3px; -} - -.dark .progressDiv{ - background: #424c5b; -} - -.progressDiv .progress{ - width: 0%; - height: 20px; - background: #0060df; - color: white; - font-weight: bold; - line-height: 20px; - padding: 0 8px 0 0; - text-align: right; - border-radius: 3px; - overflow: visible; - white-space: nowrap; - padding: 0 0.5em; -} - -.livePreview{ - position: absolute; - z-index: 300; - background-color: white; - margin: -4px; -} - -.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; -} - -.global-popup{ - display: flex; - position: fixed; - z-index: 1001; - left: 0; - top: 0; - width: 100%; - height: 100%; - overflow: auto; - background-color: rgba(20, 20, 20, 0.95); -} - - -.global-popup-close:before { - content: "×"; -} - -.global-popup-close{ - position: fixed; - right: 0.25em; - top: 0; - cursor: pointer; - color: white; - font-size: 32pt; -} - -.global-popup-inner{ - display: inline-block; - margin: auto; - padding: 2em; -} - -/* fullpage image viewer */ - -#lightboxModal{ - display: none; - position: fixed; - z-index: 1001; - left: 0; - top: 0; - width: 100%; - height: 100%; - overflow: auto; - background-color: rgba(20, 20, 20, 0.95); - user-select: none; - -webkit-user-select: none; - flex-direction: column; -} - -.modalControls { - display: flex; - gap: 1em; - padding: 1em; - background-color: rgba(0,0,0,0.2); -} -.modalClose { - margin-left: auto; -} -.modalControls span{ - color: white; - font-size: 35px; - font-weight: bold; - cursor: pointer; - width: 1em; -} - -.modalControls span:hover, .modalControls span:focus{ - color: #999; - text-decoration: none; -} - -#lightboxModal > img { - display: block; - margin: auto; - width: auto; -} - -#lightboxModal > img.modalImageFullscreen{ - object-fit: contain; - height: 100%; -} - -.modalPrev, -.modalNext { - cursor: pointer; - position: absolute; - top: 50%; - width: auto; - padding: 16px; - margin-top: -50px; - color: white; - font-weight: bold; - font-size: 20px; - transition: 0.6s ease; - border-radius: 0 3px 3px 0; - user-select: none; - -webkit-user-select: none; -} - -.modalNext { - right: 0; - border-radius: 3px 0 0 3px; -} - -.modalPrev:hover, -.modalNext:hover { - background-color: rgba(0, 0, 0, 0.8); -} - -#imageARPreview { - position: absolute; - top: 0px; - left: 0px; - border: 2px solid red; - background: rgba(255, 0, 0, 0.3); - z-index: 900; - pointer-events: none; - display: none; -} - -/* context menu (ie for the generate button) */ - -#context-menu{ - z-index:9999; - position:absolute; - display:block; - padding:0px 0; - border:2px solid #a55000; - border-radius:8px; - box-shadow:1px 1px 2px #CE6400; - width: 200px; -} - -.context-menu-items{ - list-style: none; - margin: 0; - padding: 0; -} - -.context-menu-items a{ - display:block; - padding:5px; - cursor:pointer; -} - -.context-menu-items a:hover{ - background: #a55000; -} - - -/* 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; -} - -#tab_extensions button{ - max-width: 16em; -} - -#tab_extensions input[disabled="disabled"]{ - opacity: 0.5; -} - -.extension-tag{ - font-weight: bold; - font-size: 95%; -} - -#available_extensions .info{ - margin: 0; -} - -#available_extensions .date_added{ - opacity: 0.85; - font-size: 90%; -} - -/* replace original footer with ours */ - -footer { - display: none !important; -} - -#footer{ - text-align: center; -} - -#footer div{ - display: inline-block; -} - -#footer .versions{ - font-size: 85%; - opacity: 0.85; -} - -/* extra networks UI */ -.extra-networks > div > [id *= '_extra_']{ margin: 0.3em; } -.extra-network-subdirs{ padding: 0.2em 0.35em; } -.extra-network-subdirs button{ margin: 0 0.15em; } -.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('./file=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: auto 100%; - background-position: center; - overflow: hidden; - cursor: pointer; - background-image: url('./file=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; } -.theme-preview { display: none; position: fixed; border: 4px solid var(--neutral-600); box-shadow: 2px 2px 2px 2px var(--neutral-700); top: 0; bottom: 0; left: 0; right: 0; margin: auto; max-width: 75vw; z-index: 999; } +:root, .dark{ --checkbox-label-gap: 0.25em 0.1em; --section-header-text-size: 12pt; --block-background-fill: transparent;} +.block.padded:not(.gradio-accordion) { padding: 0 !important; } +div.gradio-container{ max-width: unset !important; } +.hidden{ display: none; } +.compact{ background: transparent !important; padding: 0 !important; } +div.form{ border-width: 0; box-shadow: none; background: transparent; overflow: visible; gap: 0.5em; } +.block.gradio-dropdown, .block.gradio-slider, .block.gradio-checkbox, .block.gradio-textbox, .block.gradio-radio, .block.gradio-checkboxgroup, .block.gradio-number, .block.gradio-colorpicker { border-width: 0 !important; box-shadow: none !important;} +.gap.compact{ padding: 0; gap: 0.5em 0; } +div.compact{ gap: 1em; } +.gradio-dropdown label span:not(.has-info), .gradio-textbox label span:not(.has-info), .gradio-number label span:not(.has-info) { margin-bottom: 0; } +.gradio-dropdown ul.options{ z-index: 3000; min-width: fit-content; max-width: inherit; white-space: nowrap; } +.gradio-dropdown ul.options li.item { padding: 0.05em 0; } +.gradio-dropdown ul.options li.item:not(:has(.hide)) { background-color: var(--neutral-100); } +.dark .gradio-dropdown ul.options li.item:not(:has(.hide)) { background-color: var(--neutral-900); } +.gradio-dropdown div.wrap.wrap.wrap.wrap{ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); } +.gradio-dropdown:not(.multiselect) .wrap-inner.wrap-inner.wrap-inner{ flex-wrap: unset; } +.gradio-dropdown .single-select{ white-space: nowrap; overflow: hidden; } +.gradio-dropdown .token-remove.remove-all.remove-all{ display: none; } +.gradio-dropdown.multiselect .token-remove.remove-all.remove-all{ display: flex; } +.gradio-slider input[type="number"]{ width: 6em; } +.block.gradio-checkbox { margin: 0.75em 1.5em 0 0; } +.gradio-html div.wrap{ height: 100%; } +div.gradio-html.min{ min-height: 0; } +.block.gradio-gallery{ background: var(--input-background-fill); } +.gradio-container .prose a, .gradio-container .prose a:visited{ color: unset; text-decoration: none; } + +/* general styled components */ +.gradio-button.tool{ max-width: 2.2em; min-width: 2.2em !important; height: 2.4em; align-self: end; line-height: 1em; border-radius: 0.5em; } +.gradio-button.secondary-down{ background: var(--button-secondary-background-fill); color: var(--button-secondary-text-color); } +.gradio-button.secondary-down, .gradio-button.secondary-down:hover{ box-shadow: 1px 1px 1px rgba(0,0,0,0.25) inset, 0px 0px 3px rgba(0,0,0,0.15) inset; } +.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); + font-weight: var(--button-large-text-weight); + border: var(--button-border-width) solid var(--button-secondary-border-color); + background: var(--button-secondary-background-fill); + color: var(--button-secondary-text-color); + font-size: var(--button-large-text-size); + display: inline-flex; + justify-content: center; + align-items: center; + transition: var(--button-transition); + box-shadow: var(--button-shadow); + text-align: center; +} + +/* txt2img/img2img specific */ +.block.token-counter{ + position: absolute; + display: inline-block; + right: 1em; + min-width: 0 !important; + width: auto; + z-index: 100; + top: -0.75em; +} + +.block.token-counter span{ + background: var(--input-background-fill) !important; + box-shadow: 0 0 0.0 0.3em rgba(192,192,192,0.15), inset 0 0 0.6em rgba(192,192,192,0.075); + border: 2px solid rgba(192,192,192,0.4) !important; + border-radius: 0.4em; +} + +.block.token-counter.error span{ + box-shadow: 0 0 0.0 0.3em rgba(255,0,0,0.15), inset 0 0 0.6em rgba(255,0,0,0.075); + border: 2px solid rgba(255,0,0,0.4) !important; +} + +.block.token-counter div{ display: inline; } +.block.token-counter span{ padding: 0.1em 0.75em; } + +[id$=_subseed_show]{ + min-width: auto !important; + flex-grow: 0 !important; + display: flex; +} + +[id$=_subseed_show] label{ + margin-bottom: 0.5em; + align-self: end; +} + +.performance { + font-size: 0.85em; + color: #444; +} + +.performance p{ + display: inline-block; +} + +.performance .time { + margin-right: 0; +} + +#txt2img_generate, #img2img_generate { + min-height: 4.5em; +} + +@media screen and (min-width: 2500px) { + #txt2img_gallery, #img2img_gallery { + min-height: 768px; + } +} + +#txt2img_gallery img, #img2img_gallery img{ + object-fit: scale-down; +} +#txt2img_actions_column, #img2img_actions_column { + gap: 0.5em; +} +#txt2img_tools, #img2img_tools{ + gap: 0.4em; +} + +.interrogate-col{ + min-width: 0 !important; + max-width: fit-content; + gap: 0.5em; +} +.interrogate-col > button{ + flex: 1; +} + +.generate-box{ + position: relative; +} +.gradio-button.generate-box-skip, .gradio-button.generate-box-interrupt{ + position: absolute; + width: 50%; + height: 100%; + display: none; + background: #b4c0cc; +} +.gradio-button.generate-box-skip:hover, .gradio-button.generate-box-interrupt:hover{ + background: #c2cfdb; +} +.gradio-button.generate-box-interrupt{ + left: 0; + border-radius: 0.5rem 0 0 0.5rem; +} +.gradio-button.generate-box-skip{ + right: 0; + border-radius: 0 0.5rem 0.5rem 0; +} + +#txtimg_hr_finalres{ + min-height: 0 !important; + padding: .625rem .75rem; + margin-left: -0.75em +} + +#txtimg_hr_finalres .resolution{ + font-weight: bold; +} + +.inactive{ + opacity: 0.5; +} + +[id$=_column_batch]{ + min-width: min(13.5em, 100%) !important; +} + +div.dimensions-tools{ + min-width: 0 !important; + max-width: fit-content; + flex-direction: row; + align-content: center; +} + +div#extras_scale_to_tab div.form{ + flex-direction: row; +} + +#mode_img2img .gradio-image > div.fixed-height, #mode_img2img .gradio-image > div.fixed-height img{ + height: 480px !important; + max-height: 480px !important; + min-height: 480px !important; +} + +#img2img_sketch, #img2maskimg, #inpaint_sketch { + overflow: overlay !important; + resize: auto; + background: var(--panel-background-fill); + z-index: 5; +} + +.image-buttons button{ + min-width: auto; +} + +.infotext { + overflow-wrap: break-word; +} + +/* settings */ +#quicksettings { + width: fit-content; + align-items: end; +} + +#quicksettings > div, #quicksettings > fieldset{ + max-width: 24em; + min-width: 24em; + padding: 0; + border: none; + box-shadow: none; + background: none; +} + +#settings{ + display: block; +} + +#settings > div{ + border: none; + margin-left: 10em; +} + +#settings > div.tab-nav{ + float: left; + display: block; + margin-left: 0; + width: 10em; +} + +#settings > div.tab-nav button{ + display: block; + border: none; + text-align: left; + white-space: initial; +} + +#settings > div.tabitem { + padding-top: var(--size-1); +} + +#settings .block.gradio-checkbox { + margin-top: 0; +} + +#settings_result{ + height: 1.4em; + margin: 0 1.2em; +} + + +/* live preview */ +.progressDiv{ + position: relative; + height: 20px; + background: #b4c0cc; + border-radius: 3px !important; + margin-bottom: -3px; +} + +.dark .progressDiv{ + background: #424c5b; +} + +.progressDiv .progress{ + width: 0%; + height: 20px; + background: #0060df; + color: white; + font-weight: bold; + line-height: 20px; + padding: 0 8px 0 0; + text-align: right; + border-radius: 3px; + overflow: visible; + white-space: nowrap; + padding: 0 0.5em; +} + +.livePreview{ + position: absolute; + z-index: 300; + background-color: white; + margin: -4px; +} + +.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; +} + +.global-popup{ + display: flex; + position: fixed; + z-index: 1001; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgba(20, 20, 20, 0.95); +} + + +.global-popup-close:before { + content: "×"; +} + +.global-popup-close{ + position: fixed; + right: 0.25em; + top: 0; + cursor: pointer; + color: white; + font-size: 32pt; +} + +.global-popup-inner{ + display: inline-block; + margin: auto; + padding: 2em; +} + +/* fullpage image viewer */ + +#lightboxModal{ + display: none; + position: fixed; + z-index: 1001; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgba(20, 20, 20, 0.95); + user-select: none; + -webkit-user-select: none; + flex-direction: column; +} + +.modalControls { + display: flex; + gap: 1em; + padding: 1em; + background-color: rgba(0,0,0,0.2); +} +.modalClose { + margin-left: auto; +} +.modalControls span{ + color: white; + font-size: 35px; + font-weight: bold; + cursor: pointer; + width: 1em; +} + +.modalControls span:hover, .modalControls span:focus{ + color: #999; + text-decoration: none; +} + +#lightboxModal > img { + display: block; + margin: auto; + width: auto; +} + +#lightboxModal > img.modalImageFullscreen{ + object-fit: contain; + height: 100%; +} + +.modalPrev, +.modalNext { + cursor: pointer; + position: absolute; + top: 50%; + width: auto; + padding: 16px; + margin-top: -50px; + color: white; + font-weight: bold; + font-size: 20px; + transition: 0.6s ease; + border-radius: 0 3px 3px 0; + user-select: none; + -webkit-user-select: none; +} + +.modalNext { + right: 0; + border-radius: 3px 0 0 3px; +} + +.modalPrev:hover, +.modalNext:hover { + background-color: rgba(0, 0, 0, 0.8); +} + +#imageARPreview { + position: absolute; + top: 0px; + left: 0px; + border: 2px solid red; + background: rgba(255, 0, 0, 0.3); + z-index: 900; + pointer-events: none; + display: none; +} + +/* context menu (ie for the generate button) */ + +#context-menu{ + z-index:9999; + position:absolute; + display:block; + padding:0px 0; + border:2px solid #a55000; + border-radius:8px; + box-shadow:1px 1px 2px #CE6400; + width: 200px; +} + +.context-menu-items{ + list-style: none; + margin: 0; + padding: 0; +} + +.context-menu-items a{ + display:block; + padding:5px; + cursor:pointer; +} + +.context-menu-items a:hover{ + background: #a55000; +} + + +/* 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; +} + +#tab_extensions button{ + max-width: 16em; +} + +#tab_extensions input[disabled="disabled"]{ + opacity: 0.5; +} + +.extension-tag{ + font-weight: bold; + font-size: 95%; +} + +#available_extensions .info{ + margin: 0; +} + +#available_extensions .date_added{ + opacity: 0.85; + font-size: 90%; +} + +/* replace original footer with ours */ + +footer { + display: none !important; +} + +#footer{ + text-align: center; +} + +#footer div{ + display: inline-block; +} + +#footer .versions{ + font-size: 85%; + opacity: 0.85; +} + +/* extra networks UI */ +.extra-networks > div > [id *= '_extra_']{ margin: 0.3em; } +.extra-network-subdirs{ padding: 0.2em 0.35em; } +.extra-network-subdirs button{ margin: 0 0.15em; } +.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('./file=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: auto 100%; + background-position: center; + overflow: hidden; + cursor: pointer; + background-image: url('./file=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; } +.theme-preview { display: none; position: fixed; border: 4px solid var(--neutral-600); box-shadow: 2px 2px 2px 2px var(--neutral-700); top: 0; bottom: 0; left: 0; right: 0; margin: auto; max-width: 75vw; z-index: 999; } From 2c5ec7ce0b9d58f85d233e6c87642e369f7c1717 Mon Sep 17 00:00:00 2001 From: Alex Heller Date: Sun, 30 Apr 2023 04:07:02 +0200 Subject: [PATCH 02/31] Improvement: Added individual setting changed indicator --- javascript/ui.js | 13 +++++++++++++ modules/ui.py | 4 ++++ style.css | 15 +++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/javascript/ui.js b/javascript/ui.js index 52a9341ce..e097522d5 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -249,8 +249,21 @@ onOptionsChanged(function(){ elem.title = sd_checkpoint_hash elem.href = "https://google.com/search?q=" + sd_checkpoint_hash } + + setting_elems = gradioApp().querySelectorAll('#settings [id^="setting_"]') + setting_elems.forEach(function(elem){ + setting_name = elem.id.replace("setting_", "") + markIfModified(setting_name, opts[setting_name]) + }) }) +function markIfModified(setting_name, value) { + elem = gradioApp().getElementById("setting_"+setting_name) + if(elem == null) return; + let is_new_value = value != opts[setting_name] + elem.classList.toggle("modified", is_new_value) +} + let txt2img_textarea, img2img_textarea = undefined; let wait_time = 800 let token_timeouts = {}; diff --git a/modules/ui.py b/modules/ui.py index 12e888308..72164b5df 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1222,6 +1222,10 @@ def create_ui(): create_refresh_button(res, info.refresh, info.component_args, "refresh_" + key) else: res = comp(label=info.label, value=fun(), elem_id=elem_id, **(args or {})) + + if not is_quicksettings: + res.change(fn=None, inputs=res, outputs=[], _js=f'(val) => markIfModified("{key}", val)') + return res components = [] diff --git a/style.css b/style.css index a2f03ca7f..8327bb1cc 100644 --- a/style.css +++ b/style.css @@ -244,6 +244,21 @@ div#extras_scale_to_tab div.form{ margin-top: 0; } +#settings [id^="setting_"] { + margin-left: 1.2em; +} + +#settings [id^="setting_"].modified::before { + content: ""; + width: .25em; + position: absolute; + height: 100%; + left: -.75em; + background-color: var(--color-accent); + background-clip: content-box; + border-radius: 1em; +} + #settings_result{ height: 1.4em; margin: 0 1.2em; From ddfee28e7c11ebcb0845a506105e9d5d6372cf1a Mon Sep 17 00:00:00 2001 From: Alex Heller Date: Sun, 30 Apr 2023 23:15:53 +0200 Subject: [PATCH 03/31] Use Json serialized value to compare previous value This is done so that arrays get compared correctly (deep compare) --- javascript/ui.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/javascript/ui.js b/javascript/ui.js index e097522d5..867ccdc7e 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -260,7 +260,8 @@ onOptionsChanged(function(){ function markIfModified(setting_name, value) { elem = gradioApp().getElementById("setting_"+setting_name) if(elem == null) return; - let is_new_value = value != opts[setting_name] + // Use JSON.stringify to compare nested objects (e.g. arrays for checkbox-groups) + let is_new_value = JSON.stringify(value) != JSON.stringify(opts[setting_name]) elem.classList.toggle("modified", is_new_value) } From 2d82bc67d4a986764fb4235bf426d452ebd8bcb0 Mon Sep 17 00:00:00 2001 From: Alex Heller Date: Mon, 1 May 2023 03:38:44 +0200 Subject: [PATCH 04/31] Changed modification indicator to button element The setting value now resets to previous when clicking on the indicator. Furthermore, when hovering over the indicator it also shows what the last value was. --- javascript/ui.js | 10 +++++++--- modules/ui.py | 14 +++++++++++++- style.css | 23 +++++++++++++---------- 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/javascript/ui.js b/javascript/ui.js index 867ccdc7e..43596be3a 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -258,11 +258,15 @@ onOptionsChanged(function(){ }) function markIfModified(setting_name, value) { - elem = gradioApp().getElementById("setting_"+setting_name) + elem = gradioApp().getElementById("modification_indicator_"+setting_name) if(elem == null) return; // Use JSON.stringify to compare nested objects (e.g. arrays for checkbox-groups) - let is_new_value = JSON.stringify(value) != JSON.stringify(opts[setting_name]) - elem.classList.toggle("modified", is_new_value) + let previous_value_json = JSON.stringify(opts[setting_name]) + let is_new_value = JSON.stringify(value) != previous_value_json + elem.disabled = !is_new_value + if (is_new_value) { + elem.title = `Changed from previous value: ${previous_value_json}` + } } let txt2img_textarea, img2img_textarea = undefined; diff --git a/modules/ui.py b/modules/ui.py index 72164b5df..d31adcbf7 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1212,6 +1212,16 @@ def create_ui(): else: raise ValueError(f'bad options item type: {str(t)} for key {key}') elem_id = "setting_"+key + + if not is_quicksettings: + dirtyable_setting = gr.Group(elem_classes="dirtyable") + dirtyable_setting.__enter__() + dirty_indicator = gr.Button( + "", + elem_classes="modification-indicator", + elem_id="modification_indicator_" + key + ) + if info.refresh is not None: if is_quicksettings: res = comp(label=info.label, value=fun(), elem_id=elem_id, **(args or {})) @@ -1224,7 +1234,9 @@ def create_ui(): res = comp(label=info.label, value=fun(), elem_id=elem_id, **(args or {})) if not is_quicksettings: - res.change(fn=None, inputs=res, outputs=[], _js=f'(val) => markIfModified("{key}", val)') + res.change(fn=None, inputs=res, _js=f'(val) => markIfModified("{key}", val)') + dirty_indicator.click(fn=None, outputs=res, _js=f'() => opts["{key}"]') + dirtyable_setting.__exit__() return res diff --git a/style.css b/style.css index 8327bb1cc..ac0bf75ae 100644 --- a/style.css +++ b/style.css @@ -244,19 +244,22 @@ div#extras_scale_to_tab div.form{ margin-top: 0; } -#settings [id^="setting_"] { - margin-left: 1.2em; +#settings .dirtyable { + display: grid; + grid-template-columns: .3em auto; + gap: .5em; } -#settings [id^="setting_"].modified::before { - content: ""; - width: .25em; - position: absolute; +#settings .modification-indicator { + width: 100%; height: 100%; - left: -.75em; - background-color: var(--color-accent); - background-clip: content-box; - border-radius: 1em; + background: var(--color-accent); + border-radius: 1em !important; + padding: 0; +} + +#settings .modification-indicator:disabled { + visibility: hidden; } #settings_result{ From 074c924052fad03fd38cd50c574e828f386889e6 Mon Sep 17 00:00:00 2001 From: Alex Heller Date: Mon, 1 May 2023 04:41:29 +0200 Subject: [PATCH 05/31] Removed double empty line on hidden items This is a quick fix, needs more careful consideration --- modules/ui.py | 4 +++- style.css | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/ui.py b/modules/ui.py index d31adcbf7..04c045b9c 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1214,7 +1214,9 @@ def create_ui(): elem_id = "setting_"+key if not is_quicksettings: - dirtyable_setting = gr.Group(elem_classes="dirtyable") + # FIXME: the visibility is only copied once initially, so if the user changes it, it won't be updated + # This can probably be fixed by using a proper wrapper element + dirtyable_setting = gr.Group(elem_classes="dirtyable", visible=(args or {}).get("visible", True)) dirtyable_setting.__enter__() dirty_indicator = gr.Button( "", diff --git a/style.css b/style.css index ac0bf75ae..f484d2c29 100644 --- a/style.css +++ b/style.css @@ -250,6 +250,10 @@ div#extras_scale_to_tab div.form{ gap: .5em; } +#settings .dirtyable.hidden { + display: none; +} + #settings .modification-indicator { width: 100%; height: 100%; From 34bfd04055f3a5d6d33bbf8de7ebedb802e42e4b Mon Sep 17 00:00:00 2001 From: Alex Heller Date: Mon, 1 May 2023 10:40:23 +0200 Subject: [PATCH 06/31] Improvement: Made empty space next to check-boxes not clickable This adds the suggestion by EfourC: https://github.com/vladmandic/automatic/pull/638#issuecomment-1529416054 --- style.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/style.css b/style.css index f484d2c29..ffb8f2dbe 100644 --- a/style.css +++ b/style.css @@ -241,7 +241,8 @@ div#extras_scale_to_tab div.form{ } #settings .block.gradio-checkbox { - margin-top: 0; + margin: 0; + width: auto; } #settings .dirtyable { From ab03159e598e68879b7dbf7823838b7b01385b13 Mon Sep 17 00:00:00 2001 From: Alex Heller Date: Tue, 2 May 2023 01:02:14 +0200 Subject: [PATCH 07/31] Fix: Bar was not removed for array settings (checkbox-group) Hopefully this is the last time the behaviour needs to be adapted for that setting type. --- javascript/ui.js | 15 +++++++++++++++ modules/ui.py | 4 ++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/javascript/ui.js b/javascript/ui.js index 43596be3a..27acf1ae5 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -249,7 +249,9 @@ onOptionsChanged(function(){ elem.title = sd_checkpoint_hash elem.href = "https://google.com/search?q=" + sd_checkpoint_hash } +}) +onOptionsChanged(function(){ setting_elems = gradioApp().querySelectorAll('#settings [id^="setting_"]') setting_elems.forEach(function(elem){ setting_name = elem.id.replace("setting_", "") @@ -269,6 +271,19 @@ function markIfModified(setting_name, value) { } } +function onSettingComponentChanged(setting_name, value) { + markIfModified(setting_name, value) + return [] +} + +function onModificationIndicatorClicked(setting_name) { + elem = gradioApp().getElementById("modification_indicator_"+setting_name) + if (elem) { + elem.disabled = true + } + return JSON.parse(JSON.stringify(opts[setting_name])); +} + let txt2img_textarea, img2img_textarea = undefined; let wait_time = 800 let token_timeouts = {}; diff --git a/modules/ui.py b/modules/ui.py index 04c045b9c..4e9228812 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1236,8 +1236,8 @@ def create_ui(): res = comp(label=info.label, value=fun(), elem_id=elem_id, **(args or {})) if not is_quicksettings: - res.change(fn=None, inputs=res, _js=f'(val) => markIfModified("{key}", val)') - dirty_indicator.click(fn=None, outputs=res, _js=f'() => opts["{key}"]') + res.change(fn=None, inputs=res, _js=f'(val) => onSettingComponentChanged("{key}", val)') + dirty_indicator.click(fn=None, outputs=res, _js=f'() => onModificationIndicatorClicked("{key}")') dirtyable_setting.__exit__() return res From 947b1991e0dcfadb8424cc633a910653df4397c9 Mon Sep 17 00:00:00 2001 From: Alex Heller Date: Wed, 3 May 2023 14:30:08 +0200 Subject: [PATCH 08/31] Refactor: Removed unused group in settings --- modules/ui.py | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/ui.py b/modules/ui.py index 4e9228812..e2e788481 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1304,7 +1304,6 @@ def create_ui(): if current_tab is not None: current_row.__exit__() current_tab.__exit__() - gr.Group() current_tab = gr.TabItem(elem_id=f"settings_{elem_id}", label=text) current_tab.__enter__() current_row = gr.Column(variant='compact') From a48e2b90a5970e287ab4138bb683eb48cd06b3ed Mon Sep 17 00:00:00 2001 From: Alex Heller Date: Sun, 7 May 2023 23:21:37 +0200 Subject: [PATCH 09/31] Added visual toplevel setting change indicators --- javascript/ui.js | 20 ++++++++++++++++++++ style.css | 14 ++++++++++---- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/javascript/ui.js b/javascript/ui.js index 27acf1ae5..fd2ed1ee3 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -259,6 +259,26 @@ onOptionsChanged(function(){ }) }) +onUiLoaded(function(){ + tab_nav_element = gradioApp().querySelector('#settings > .tab-nav') + tab_nav_buttons = gradioApp().querySelectorAll('#settings > .tab-nav > button') + tab_elements = gradioApp().querySelectorAll('#settings > [id^="settings_"]') + + tab_elements.forEach(function(elem, index){ + // Add a modification indicator to the toplevel tab button + let new_indicator = document.createElement('button') + new_indicator.id = "modification_indicator_"+elem.id.replace("setting_", "") + new_indicator.className = "modification-indicator" + new_indicator.disabled = true + + // Add a modification indicator to the toplevel tab button + tab_nav_element.insertBefore(new_indicator, tab_nav_buttons[index]) + }) + // Add the dirtyable class to the tab nav element + tab_nav_element.classList.add('dirtyable') +}) + + function markIfModified(setting_name, value) { elem = gradioApp().getElementById("modification_indicator_"+setting_name) if(elem == null) return; diff --git a/style.css b/style.css index ffb8f2dbe..1d0d8b030 100644 --- a/style.css +++ b/style.css @@ -219,14 +219,14 @@ div#extras_scale_to_tab div.form{ #settings > div{ border: none; - margin-left: 10em; + margin-left: 11em; } #settings > div.tab-nav{ float: left; - display: block; margin-left: 0; - width: 10em; + display: grid; + grid-template-columns: .3em 10em; } #settings > div.tab-nav button{ @@ -234,10 +234,16 @@ div#extras_scale_to_tab div.form{ border: none; text-align: left; white-space: initial; + padding: 0; +} + +#settings > div.tab-nav > #settings_show_all_pages { + grid-column: 2; + padding: var(--size-2) var(--size-4); } #settings > div.tabitem { - padding-top: var(--size-1); + padding-top: 0; } #settings .block.gradio-checkbox { From 16b5281d1306b5288b67dc8a455a832c3c61b62f Mon Sep 17 00:00:00 2001 From: Alex Heller Date: Mon, 8 May 2023 00:09:53 +0200 Subject: [PATCH 10/31] Added toplevel change indicator functionality --- javascript/ui.js | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/javascript/ui.js b/javascript/ui.js index fd2ed1ee3..f32fe959e 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -267,9 +267,10 @@ onUiLoaded(function(){ tab_elements.forEach(function(elem, index){ // Add a modification indicator to the toplevel tab button let new_indicator = document.createElement('button') - new_indicator.id = "modification_indicator_"+elem.id.replace("setting_", "") + new_indicator.id = "modification_indicator_"+elem.id new_indicator.className = "modification-indicator" new_indicator.disabled = true + new_indicator.onclick = () => onSettingsModificationIndicatorClicked(elem.id) // Add a modification indicator to the toplevel tab button tab_nav_element.insertBefore(new_indicator, tab_nav_buttons[index]) @@ -279,6 +280,7 @@ onUiLoaded(function(){ }) +dirty_opts = {} function markIfModified(setting_name, value) { elem = gradioApp().getElementById("modification_indicator_"+setting_name) if(elem == null) return; @@ -289,6 +291,29 @@ function markIfModified(setting_name, value) { if (is_new_value) { elem.title = `Changed from previous value: ${previous_value_json}` } + + // Get parent tab element + let tab_element = elem.closest('[id^="settings_"]') + if (tab_element == null) return; + let tab_name = tab_element.id + if (dirty_opts[tab_name] == undefined) { + dirty_opts[tab_name] = new Set() + } + if (is_new_value) { + dirty_opts[tab_name].add(setting_name) + } else { + dirty_opts[tab_name].delete(setting_name) + } + let is_tab_dirty = dirty_opts[tab_name].size > 0 + + // Set the indicator on the tab nav element + let tab_nav_indicator = tab_nav_element.querySelector('#modification_indicator_'+tab_name) + tab_nav_indicator.disabled = !is_tab_dirty + if (dirty_opts[tab_name].size > 1) { + tab_nav_indicator.title = `Click to reset ${dirty_opts[tab_name].size} unapplied changes in this tab.` + } else if (dirty_opts[tab_name].size == 1) { + tab_nav_indicator.title = `Click to reset 1 unapplied change in this tab.` + } } function onSettingComponentChanged(setting_name, value) { @@ -296,11 +321,15 @@ function onSettingComponentChanged(setting_name, value) { return [] } +function onSettingsModificationIndicatorClicked(tab_name) { + dirty_opts[tab_name].forEach(function(setting_name){ + // Click each setting's modification indicator + gradioApp().getElementById("modification_indicator_"+setting_name).click() + }) +} + function onModificationIndicatorClicked(setting_name) { - elem = gradioApp().getElementById("modification_indicator_"+setting_name) - if (elem) { - elem.disabled = true - } + markIfModified(setting_name, opts[setting_name]) return JSON.parse(JSON.stringify(opts[setting_name])); } From 47a19f1f831450135354f65ec43951a25134bdc2 Mon Sep 17 00:00:00 2001 From: Alex Heller Date: Mon, 8 May 2023 00:11:09 +0200 Subject: [PATCH 11/31] Slightly changed hover information Now it's more in line with the toplevel indicator message. --- javascript/ui.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ui.js b/javascript/ui.js index f32fe959e..46061d214 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -289,7 +289,7 @@ function markIfModified(setting_name, value) { let is_new_value = JSON.stringify(value) != previous_value_json elem.disabled = !is_new_value if (is_new_value) { - elem.title = `Changed from previous value: ${previous_value_json}` + elem.title = `Click to revert to previous value: ${previous_value_json}` } // Get parent tab element From 1fdbab5e64e90068a5adcb7f555c8c96d288506c Mon Sep 17 00:00:00 2001 From: Alex Heller Date: Mon, 8 May 2023 00:15:51 +0200 Subject: [PATCH 12/31] Fix: Issue with show all setting pages --- javascript/ui.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/javascript/ui.js b/javascript/ui.js index 46061d214..cedef18bb 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -228,11 +228,11 @@ onUiUpdate(function(){ registerTextarea('img2img_prompt', 'img2img_token_counter', 'img2img_token_button') registerTextarea('img2img_neg_prompt', 'img2img_negative_token_counter', 'img2img_negative_token_button') show_all_pages = gradioApp().getElementById('settings_show_all_pages') - settings_tabs = gradioApp().querySelector('#settings div') + settings_tabs = gradioApp().querySelector('#settings > div.tab-nav') if(show_all_pages && settings_tabs){ settings_tabs.appendChild(show_all_pages) show_all_pages.onclick = function(){ - gradioApp().querySelectorAll('#settings > div').forEach(function(elem){ + gradioApp().querySelectorAll('#settings > div[id^="settings_"]').forEach(function(elem){ elem.style.display = "block"; }) } From d2108ab2b919bd1d206a769531ded71e3644b0c0 Mon Sep 17 00:00:00 2001 From: Alex Heller Date: Wed, 17 May 2023 03:40:16 +0200 Subject: [PATCH 13/31] Made setting tabs move to top if not enough space Currently the tabs will be ordered in grid fashion, but might be worth considering to somehow bring back the original flex tab layout. This is hard though due to the interleaved modification indicators, and the inability to wrap the tab headers in a custom div (as gradio's svelte replaces the wrapper with each ui update) --- javascript/ui.js | 34 +++++++++++++++++++++++----------- style.css | 28 +++++++++++++++++----------- 2 files changed, 40 insertions(+), 22 deletions(-) diff --git a/javascript/ui.js b/javascript/ui.js index cedef18bb..bb774cf37 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -227,16 +227,6 @@ onUiUpdate(function(){ registerTextarea('txt2img_neg_prompt', 'txt2img_negative_token_counter', 'txt2img_negative_token_button') registerTextarea('img2img_prompt', 'img2img_token_counter', 'img2img_token_button') registerTextarea('img2img_neg_prompt', 'img2img_negative_token_counter', 'img2img_negative_token_button') - show_all_pages = gradioApp().getElementById('settings_show_all_pages') - settings_tabs = gradioApp().querySelector('#settings > div.tab-nav') - if(show_all_pages && settings_tabs){ - settings_tabs.appendChild(show_all_pages) - show_all_pages.onclick = function(){ - gradioApp().querySelectorAll('#settings > div[id^="settings_"]').forEach(function(elem){ - elem.style.display = "block"; - }) - } - } }) onOptionsChanged(function(){ @@ -262,7 +252,12 @@ onOptionsChanged(function(){ onUiLoaded(function(){ tab_nav_element = gradioApp().querySelector('#settings > .tab-nav') tab_nav_buttons = gradioApp().querySelectorAll('#settings > .tab-nav > button') - tab_elements = gradioApp().querySelectorAll('#settings > [id^="settings_"]') + tab_elements = gradioApp().querySelectorAll('#settings > div:not(.tab-nav)') + + // Add a wrapper for the tab content (everything but the tab nav) + const tab_content_wrapper = document.createElement('div') + tab_content_wrapper.className = "tab-content" + tab_nav_element.parentElement.insertBefore(tab_content_wrapper, tab_nav_element.nextSibling) tab_elements.forEach(function(elem, index){ // Add a modification indicator to the toplevel tab button @@ -274,7 +269,24 @@ onUiLoaded(function(){ // Add a modification indicator to the toplevel tab button tab_nav_element.insertBefore(new_indicator, tab_nav_buttons[index]) + + // Add the tab content to the wrapper + tab_content_wrapper.appendChild(elem) }) + + // Add show all pages button + const show_all_pages = gradioApp().getElementById('settings_show_all_pages') + tab_nav_element.appendChild(show_all_pages) + show_all_pages.onclick = function(){ + gradioApp().querySelectorAll('#settings [id^="settings_"]').forEach(function(elem){ + elem.style.display = "block"; + }) + } + + // HACK: Add empty span inplace of modification indicator infront of show all button + const dummy_indicator = document.createElement('span') + tab_nav_element.insertBefore(dummy_indicator, show_all_pages) + // Add the dirtyable class to the tab nav element tab_nav_element.classList.add('dirtyable') }) diff --git a/style.css b/style.css index 1d0d8b030..cef5afdda 100644 --- a/style.css +++ b/style.css @@ -214,19 +214,30 @@ div#extras_scale_to_tab div.form{ } #settings{ - display: block; + display: flex; + flex-flow: row wrap; + gap: var(--layout-gap); } -#settings > div{ +#settings div { border: none; - margin-left: 11em; +} + +#settings > div.tab-content { + flex: 100000 0 75%; +} + +#settings > div.tab-content > div{ + border: none; + padding: 0; } #settings > div.tab-nav{ - float: left; - margin-left: 0; display: grid; - grid-template-columns: .3em 10em; + grid-template-columns: repeat(auto-fill, .3em minmax(10em, 1fr)); + flex: 1 0 auto; + width: 11em; + align-self: flex-start } #settings > div.tab-nav button{ @@ -238,14 +249,9 @@ div#extras_scale_to_tab div.form{ } #settings > div.tab-nav > #settings_show_all_pages { - grid-column: 2; padding: var(--size-2) var(--size-4); } -#settings > div.tabitem { - padding-top: 0; -} - #settings .block.gradio-checkbox { margin: 0; width: auto; From f4cd452c149f540b242ed983c34e3a371dca289b Mon Sep 17 00:00:00 2001 From: Alex Heller Date: Sat, 20 May 2023 00:18:46 +0200 Subject: [PATCH 14/31] Made show all settings a tab instead of a separate button This makes it play more nicely with the other tabs. It still needs some js though to keep the other setting tabs open, when resetting settings. --- javascript/ui.js | 30 ++++++++++++++++++------------ modules/ui.py | 3 ++- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/javascript/ui.js b/javascript/ui.js index bb774cf37..408f98d91 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -254,6 +254,21 @@ onUiLoaded(function(){ tab_nav_buttons = gradioApp().querySelectorAll('#settings > .tab-nav > button') tab_elements = gradioApp().querySelectorAll('#settings > div:not(.tab-nav)') + // Add mutation observer to to all tab items + // HACK to keep gradio from closing when showing all pages + const observer = new MutationObserver(function(mutations) { + const show_all_pages_dummy = gradioApp().getElementById('settings_show_all_pages') + mutations.forEach(function(mutation) { + if (mutation.type === 'attributes' && mutation.attributeName === 'style') { + if (show_all_pages_dummy.style.display != "none") { + gradioApp().querySelectorAll('#settings [id^="settings_"]').forEach(function(elem){ + elem.style.display = "block"; + }) + } + } + }) + }) + // Add a wrapper for the tab content (everything but the tab nav) const tab_content_wrapper = document.createElement('div') tab_content_wrapper.className = "tab-content" @@ -272,20 +287,11 @@ onUiLoaded(function(){ // Add the tab content to the wrapper tab_content_wrapper.appendChild(elem) + + // Add the mutation observer to the tab element + observer.observe(elem, { attributes: true, attributeFilter: ['style'] }) }) - // Add show all pages button - const show_all_pages = gradioApp().getElementById('settings_show_all_pages') - tab_nav_element.appendChild(show_all_pages) - show_all_pages.onclick = function(){ - gradioApp().querySelectorAll('#settings [id^="settings_"]').forEach(function(elem){ - elem.style.display = "block"; - }) - } - - // HACK: Add empty span inplace of modification indicator infront of show all button - const dummy_indicator = document.createElement('span') - tab_nav_element.insertBefore(dummy_indicator, show_all_pages) // Add the dirtyable class to the tab nav element tab_nav_element.classList.add('dirtyable') diff --git a/modules/ui.py b/modules/ui.py index e2e788481..cde0180ef 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1323,9 +1323,10 @@ def create_ui(): current_tab.__exit__() request_notifications = gr.Button(value='Request browser notifications', elem_id="request_notifications", visible=False) - _show_all_pages = gr.Button(value="Show all pages", variant='primary', elem_id="settings_show_all_pages") with gr.TabItem("Licenses"): gr.HTML(shared.html("licenses.html"), elem_id="licenses") + with gr.TabItem("Show all pages", variant='primary', elem_id="settings_show_all_pages"): + pass def unload_sd_weights(): modules.sd_models.unload_model_weights() From 6c8e7a75c731efbe01eba0fbaebdc8d1529ec945 Mon Sep 17 00:00:00 2001 From: Alex Heller Date: Sun, 4 Jun 2023 04:39:48 +0200 Subject: [PATCH 15/31] Added initial sync of unstored parameters to the UI. --- javascript/ui.js | 30 ++++++++++++++++++++---------- modules/shared.py | 2 +- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/javascript/ui.js b/javascript/ui.js index 408f98d91..b6556947c 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -191,20 +191,27 @@ function recalculate_prompts_img2img(){ } opts = {} +stored_opts = {} +function parseOpts(json_string){ + let settings_data = JSON.parse(json_string) + opts = settings_data.values + stored_opts = new Set(settings_data.stored_values) +} + onUiUpdate(function(){ if(Object.keys(opts).length != 0) return; json_elem = gradioApp().getElementById('settings_json') if(json_elem == null) return; var textarea = json_elem.querySelector('textarea') var jsdata = textarea.value - opts = JSON.parse(jsdata) + parseOpts(jsdata) executeCallbacks(optionsChangedCallbacks); Object.defineProperty(textarea, 'value', { set: function(newValue) { var valueProp = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value'); var oldValue = valueProp.get.call(textarea); valueProp.set.call(textarea, newValue); - if (oldValue != newValue) opts = JSON.parse(textarea.value) + if (oldValue != newValue) parseOpts(textarea.value) executeCallbacks(optionsChangedCallbacks); }, get: function() { @@ -304,12 +311,19 @@ function markIfModified(setting_name, value) { if(elem == null) return; // Use JSON.stringify to compare nested objects (e.g. arrays for checkbox-groups) let previous_value_json = JSON.stringify(opts[setting_name]) - let is_new_value = JSON.stringify(value) != previous_value_json - elem.disabled = !is_new_value - if (is_new_value) { + let changed_value = JSON.stringify(value) != previous_value_json + if (changed_value) { elem.title = `Click to revert to previous value: ${previous_value_json}` } + let unsaved_value = !stored_opts.has(setting_name) + if (unsaved_value) { + elem.title = 'Unsaved value, apply settings to save.'; + } + // TODO properly propagate unsaved value message to tab indicator / color marker differntly? + let is_dirty = unsaved_value || changed_value + elem.disabled = !is_dirty + // Get parent tab element let tab_element = elem.closest('[id^="settings_"]') if (tab_element == null) return; @@ -317,11 +331,7 @@ function markIfModified(setting_name, value) { if (dirty_opts[tab_name] == undefined) { dirty_opts[tab_name] = new Set() } - if (is_new_value) { - dirty_opts[tab_name].add(setting_name) - } else { - dirty_opts[tab_name].delete(setting_name) - } + is_dirty ? dirty_opts[tab_name].add(setting_name) : dirty_opts[tab_name].delete(setting_name) let is_tab_dirty = dirty_opts[tab_name].size > 0 // Set the indicator on the tab nav element diff --git a/modules/shared.py b/modules/shared.py index f3ebbce55..8b991ea81 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -544,7 +544,7 @@ class Options: def dumpjson(self): d = {k: self.data.get(k, self.data_labels.get(k).default) for k in self.data_labels.keys()} - return json.dumps(d) + return json.dumps({"values": d, "stored_values": list(self.data.keys())}) def add_option(self, key, info): self.data_labels[key] = info From c6f50fcf4a42420483f3a0d51d48b661a859c451 Mon Sep 17 00:00:00 2001 From: Alex Heller Date: Sun, 4 Jun 2023 04:45:21 +0200 Subject: [PATCH 16/31] Fixed wrong type for init of stored settings in ui.js --- javascript/ui.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ui.js b/javascript/ui.js index b6556947c..16d763c8c 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -191,7 +191,7 @@ function recalculate_prompts_img2img(){ } opts = {} -stored_opts = {} +stored_opts = new Set() function parseOpts(json_string){ let settings_data = JSON.parse(json_string) opts = settings_data.values From ba5b46b570289a567197f7c915d9bce247646f1b Mon Sep 17 00:00:00 2001 From: Alex Heller Date: Sun, 4 Jun 2023 17:15:38 +0200 Subject: [PATCH 17/31] Removed repeated calls to unfold other tabs when in show all mode. --- javascript/ui.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/javascript/ui.js b/javascript/ui.js index 16d763c8c..3647864b3 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -265,15 +265,14 @@ onUiLoaded(function(){ // HACK to keep gradio from closing when showing all pages const observer = new MutationObserver(function(mutations) { const show_all_pages_dummy = gradioApp().getElementById('settings_show_all_pages') - mutations.forEach(function(mutation) { - if (mutation.type === 'attributes' && mutation.attributeName === 'style') { - if (show_all_pages_dummy.style.display != "none") { - gradioApp().querySelectorAll('#settings [id^="settings_"]').forEach(function(elem){ - elem.style.display = "block"; - }) - } - } - }) + if (show_all_pages_dummy.style.display == "none") + return; + function mutation_on_style(mut) { + return mut.type === 'attributes' && mut.attributeName === 'style' + } + if (mutations.some(mutation_on_style)) { + tab_elements.forEach((elem) => elem.style.display = "block") + } }) // Add a wrapper for the tab content (everything but the tab nav) From 993da0a6804a42a12f06d15b8582ecaedcd2431b Mon Sep 17 00:00:00 2001 From: Alex Heller Date: Sun, 4 Jun 2023 17:46:15 +0200 Subject: [PATCH 18/31] Some cleanup on the frontend side --- javascript/ui.js | 9 +-------- style.css | 3 ++- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/javascript/ui.js b/javascript/ui.js index 3647864b3..4d8b00faa 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -261,8 +261,7 @@ onUiLoaded(function(){ tab_nav_buttons = gradioApp().querySelectorAll('#settings > .tab-nav > button') tab_elements = gradioApp().querySelectorAll('#settings > div:not(.tab-nav)') - // Add mutation observer to to all tab items - // HACK to keep gradio from closing when showing all pages + // HACK Add mutation observer to keep gradio from closing setting tabs when showing all pages const observer = new MutationObserver(function(mutations) { const show_all_pages_dummy = gradioApp().getElementById('settings_show_all_pages') if (show_all_pages_dummy.style.display == "none") @@ -287,8 +286,6 @@ onUiLoaded(function(){ new_indicator.className = "modification-indicator" new_indicator.disabled = true new_indicator.onclick = () => onSettingsModificationIndicatorClicked(elem.id) - - // Add a modification indicator to the toplevel tab button tab_nav_element.insertBefore(new_indicator, tab_nav_buttons[index]) // Add the tab content to the wrapper @@ -297,10 +294,6 @@ onUiLoaded(function(){ // Add the mutation observer to the tab element observer.observe(elem, { attributes: true, attributeFilter: ['style'] }) }) - - - // Add the dirtyable class to the tab nav element - tab_nav_element.classList.add('dirtyable') }) diff --git a/style.css b/style.css index cef5afdda..bfd2c229b 100644 --- a/style.css +++ b/style.css @@ -237,7 +237,8 @@ div#extras_scale_to_tab div.form{ grid-template-columns: repeat(auto-fill, .3em minmax(10em, 1fr)); flex: 1 0 auto; width: 11em; - align-self: flex-start + align-self: flex-start; + gap: var(--spacing-md); } #settings > div.tab-nav button{ From 9da0f71f9b01ec716ceaa5085ca10362a09a0c30 Mon Sep 17 00:00:00 2001 From: Alex Heller Date: Sun, 4 Jun 2023 18:19:37 +0200 Subject: [PATCH 19/31] Removed some variables from global scope. --- javascript/ui.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/javascript/ui.js b/javascript/ui.js index 4d8b00faa..a91c48d4f 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -249,7 +249,7 @@ onOptionsChanged(function(){ }) onOptionsChanged(function(){ - setting_elems = gradioApp().querySelectorAll('#settings [id^="setting_"]') + let setting_elems = gradioApp().querySelectorAll('#settings [id^="setting_"]') setting_elems.forEach(function(elem){ setting_name = elem.id.replace("setting_", "") markIfModified(setting_name, opts[setting_name]) @@ -257,9 +257,9 @@ onOptionsChanged(function(){ }) onUiLoaded(function(){ - tab_nav_element = gradioApp().querySelector('#settings > .tab-nav') - tab_nav_buttons = gradioApp().querySelectorAll('#settings > .tab-nav > button') - tab_elements = gradioApp().querySelectorAll('#settings > div:not(.tab-nav)') + let tab_nav_element = gradioApp().querySelector('#settings > .tab-nav') + let tab_nav_buttons = gradioApp().querySelectorAll('#settings > .tab-nav > button') + let tab_elements = gradioApp().querySelectorAll('#settings > div:not(.tab-nav)') // HACK Add mutation observer to keep gradio from closing setting tabs when showing all pages const observer = new MutationObserver(function(mutations) { @@ -296,10 +296,9 @@ onUiLoaded(function(){ }) }) - dirty_opts = {} function markIfModified(setting_name, value) { - elem = gradioApp().getElementById("modification_indicator_"+setting_name) + let elem = gradioApp().getElementById("modification_indicator_"+setting_name) if(elem == null) return; // Use JSON.stringify to compare nested objects (e.g. arrays for checkbox-groups) let previous_value_json = JSON.stringify(opts[setting_name]) @@ -327,7 +326,7 @@ function markIfModified(setting_name, value) { let is_tab_dirty = dirty_opts[tab_name].size > 0 // Set the indicator on the tab nav element - let tab_nav_indicator = tab_nav_element.querySelector('#modification_indicator_'+tab_name) + let tab_nav_indicator = gradioApp().querySelector('#settings > .tab-nav > #modification_indicator_'+tab_name) tab_nav_indicator.disabled = !is_tab_dirty if (dirty_opts[tab_name].size > 1) { tab_nav_indicator.title = `Click to reset ${dirty_opts[tab_name].size} unapplied changes in this tab.` From a1881ff0cd311f0c16d0534933ac16ff28040814 Mon Sep 17 00:00:00 2001 From: Alex Heller Date: Sun, 4 Jun 2023 23:40:11 +0200 Subject: [PATCH 20/31] Consolidated options metadata in frontend ui.js --- javascript/ui.js | 35 ++++++++++++++--------------------- modules/shared.py | 2 +- 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/javascript/ui.js b/javascript/ui.js index a91c48d4f..a1be9aa06 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -191,11 +191,13 @@ function recalculate_prompts_img2img(){ } opts = {} -stored_opts = new Set() -function parseOpts(json_string){ +opts_metadata = {changed_tab_items: {}, is_default: {}} +function updateOpts(json_string){ let settings_data = JSON.parse(json_string) opts = settings_data.values - stored_opts = new Set(settings_data.stored_values) + Object.keys(opts).forEach(function(key){ + opts_metadata.is_default[key] = settings_data.stored_keys.indexOf(key) != -1 + }) } onUiUpdate(function(){ @@ -204,14 +206,14 @@ onUiUpdate(function(){ if(json_elem == null) return; var textarea = json_elem.querySelector('textarea') var jsdata = textarea.value - parseOpts(jsdata) + updateOpts(jsdata) executeCallbacks(optionsChangedCallbacks); Object.defineProperty(textarea, 'value', { set: function(newValue) { var valueProp = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value'); var oldValue = valueProp.get.call(textarea); valueProp.set.call(textarea, newValue); - if (oldValue != newValue) parseOpts(textarea.value) + if (oldValue != newValue) updateOpts(textarea.value) executeCallbacks(optionsChangedCallbacks); }, get: function() { @@ -296,7 +298,6 @@ onUiLoaded(function(){ }) }) -dirty_opts = {} function markIfModified(setting_name, value) { let elem = gradioApp().getElementById("modification_indicator_"+setting_name) if(elem == null) return; @@ -307,7 +308,7 @@ function markIfModified(setting_name, value) { elem.title = `Click to revert to previous value: ${previous_value_json}` } - let unsaved_value = !stored_opts.has(setting_name) + let unsaved_value = !opts_metadata.is_default[setting_name] if (unsaved_value) { elem.title = 'Unsaved value, apply settings to save.'; } @@ -315,24 +316,16 @@ function markIfModified(setting_name, value) { let is_dirty = unsaved_value || changed_value elem.disabled = !is_dirty - // Get parent tab element let tab_element = elem.closest('[id^="settings_"]') if (tab_element == null) return; let tab_name = tab_element.id - if (dirty_opts[tab_name] == undefined) { - dirty_opts[tab_name] = new Set() - } - is_dirty ? dirty_opts[tab_name].add(setting_name) : dirty_opts[tab_name].delete(setting_name) - let is_tab_dirty = dirty_opts[tab_name].size > 0 + let dirty_tab_items = (opts_metadata.changed_tab_items[tab_name] ||= new Set()) + changed_value ? dirty_tab_items.add(setting_name) : dirty_tab_items.delete(setting_name) // Set the indicator on the tab nav element - let tab_nav_indicator = gradioApp().querySelector('#settings > .tab-nav > #modification_indicator_'+tab_name) - tab_nav_indicator.disabled = !is_tab_dirty - if (dirty_opts[tab_name].size > 1) { - tab_nav_indicator.title = `Click to reset ${dirty_opts[tab_name].size} unapplied changes in this tab.` - } else if (dirty_opts[tab_name].size == 1) { - tab_nav_indicator.title = `Click to reset 1 unapplied change in this tab.` - } + let tab_nav_indicator = gradioApp().getElementById('modification_indicator_'+tab_name) + tab_nav_indicator.disabled = dirty_tab_items.size == 0 + tab_nav_indicator.title = `Click to reset ${dirty_tab_items.size} unapplied change${dirty_tab_items.size > 1 ? 's': ''} in this tab.` } function onSettingComponentChanged(setting_name, value) { @@ -341,7 +334,7 @@ function onSettingComponentChanged(setting_name, value) { } function onSettingsModificationIndicatorClicked(tab_name) { - dirty_opts[tab_name].forEach(function(setting_name){ + opts_metadata.changed_tab_items[tab_name].forEach(function(setting_name){ // Click each setting's modification indicator gradioApp().getElementById("modification_indicator_"+setting_name).click() }) diff --git a/modules/shared.py b/modules/shared.py index 8b991ea81..a58a974b3 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -544,7 +544,7 @@ class Options: def dumpjson(self): d = {k: self.data.get(k, self.data_labels.get(k).default) for k in self.data_labels.keys()} - return json.dumps({"values": d, "stored_values": list(self.data.keys())}) + return json.dumps({"values": d, "stored_keys": list(self.data.keys())}) def add_option(self, key, info): self.data_labels[key] = info From 8c6ed7fe11f46682f5032b5fe7b99b6e8be8b0df Mon Sep 17 00:00:00 2001 From: Alex Heller Date: Mon, 5 Jun 2023 00:57:30 +0200 Subject: [PATCH 21/31] Added support for differentiation between "default unsaved" and "changed" settings. --- javascript/ui.js | 46 ++++++++++++++++++++++++++++------------------ modules/shared.py | 8 +++++++- style.css | 12 ++++++++++++ 3 files changed, 47 insertions(+), 19 deletions(-) diff --git a/javascript/ui.js b/javascript/ui.js index a1be9aa06..4995ad558 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -191,12 +191,16 @@ function recalculate_prompts_img2img(){ } opts = {} -opts_metadata = {changed_tab_items: {}, is_default: {}} +opts_metadata = {} +opts_tabs = {} function updateOpts(json_string){ let settings_data = JSON.parse(json_string) opts = settings_data.values - Object.keys(opts).forEach(function(key){ - opts_metadata.is_default[key] = settings_data.stored_keys.indexOf(key) != -1 + opts_metadata = settings_data.metadata + Object.entries(opts_metadata).forEach(([opt, meta]) => { + opts_tabs[meta.tab_name] ||= {} + let unsaved = (opts_tabs[meta.tab_name].unsaved_keys ||= new Set()) + if (!meta.is_stored) unsaved.add(opt) }) } @@ -283,11 +287,12 @@ onUiLoaded(function(){ tab_elements.forEach(function(elem, index){ // Add a modification indicator to the toplevel tab button + let tab_name = elem.id.replace("settings_", "") let new_indicator = document.createElement('button') - new_indicator.id = "modification_indicator_"+elem.id + new_indicator.id = "modification_indicator_"+tab_name new_indicator.className = "modification-indicator" new_indicator.disabled = true - new_indicator.onclick = () => onSettingsModificationIndicatorClicked(elem.id) + new_indicator.onclick = () => onSettingsModificationIndicatorClicked(tab_name) tab_nav_element.insertBefore(new_indicator, tab_nav_buttons[index]) // Add the tab content to the wrapper @@ -308,24 +313,29 @@ function markIfModified(setting_name, value) { elem.title = `Click to revert to previous value: ${previous_value_json}` } - let unsaved_value = !opts_metadata.is_default[setting_name] - if (unsaved_value) { + is_unsaved = !opts_metadata[setting_name].is_stored + if (is_unsaved) { elem.title = 'Unsaved value, apply settings to save.'; } - // TODO properly propagate unsaved value message to tab indicator / color marker differntly? - let is_dirty = unsaved_value || changed_value - elem.disabled = !is_dirty + elem.disabled = !(is_unsaved || changed_value) + elem.classList.toggle('changed', changed_value) + elem.classList.toggle('unsaved', is_unsaved) - let tab_element = elem.closest('[id^="settings_"]') - if (tab_element == null) return; - let tab_name = tab_element.id - let dirty_tab_items = (opts_metadata.changed_tab_items[tab_name] ||= new Set()) - changed_value ? dirty_tab_items.add(setting_name) : dirty_tab_items.delete(setting_name) + let tab_name = opts_metadata[setting_name].tab_name + let changed_items = (opts_tabs[tab_name].changed ||= new Set()) + changed_value ? changed_items.add(setting_name) : changed_items.delete(setting_name) + let unsaved = opts_tabs[tab_name].unsaved_keys // Set the indicator on the tab nav element let tab_nav_indicator = gradioApp().getElementById('modification_indicator_'+tab_name) - tab_nav_indicator.disabled = dirty_tab_items.size == 0 - tab_nav_indicator.title = `Click to reset ${dirty_tab_items.size} unapplied change${dirty_tab_items.size > 1 ? 's': ''} in this tab.` + tab_nav_indicator.disabled = (changed_items.size == 0) && (unsaved.size == 0) + tab_nav_indicator.title = ''; + tab_nav_indicator.classList.toggle('changed', changed_items.size > 0) + tab_nav_indicator.classList.toggle('unsaved', unsaved.size > 0) + if (changed_items.size > 0) + tab_nav_indicator.title += `Click to reset ${changed_items.size} unapplied change${changed_items.size > 1 ? 's': ''} in this tab.\n` + if (unsaved.size > 0) + tab_nav_indicator.title += `Apply settings to save ${unsaved.size} unsaved value${unsaved.size > 1 ? 's':''}.`; } function onSettingComponentChanged(setting_name, value) { @@ -334,7 +344,7 @@ function onSettingComponentChanged(setting_name, value) { } function onSettingsModificationIndicatorClicked(tab_name) { - opts_metadata.changed_tab_items[tab_name].forEach(function(setting_name){ + opts_tabs[tab_name].changed.forEach(function(setting_name){ // Click each setting's modification indicator gradioApp().getElementById("modification_indicator_"+setting_name).click() }) diff --git a/modules/shared.py b/modules/shared.py index a58a974b3..1fa69a66c 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -544,7 +544,13 @@ class Options: def dumpjson(self): d = {k: self.data.get(k, self.data_labels.get(k).default) for k in self.data_labels.keys()} - return json.dumps({"values": d, "stored_keys": list(self.data.keys())}) + metadata = { + k: { + "is_stored": k in self.data, + "tab_name": v.section[0] + } for k, v in self.data_labels.items() + } + return json.dumps({"values": d, "metadata": metadata}) def add_option(self, key, info): self.data_labels[key] = info diff --git a/style.css b/style.css index bfd2c229b..0c6098fe9 100644 --- a/style.css +++ b/style.css @@ -280,6 +280,18 @@ div#extras_scale_to_tab div.form{ visibility: hidden; } +#settings .modification-indicator.unsaved { + background: var(--color-accent-soft); +} + +#settings .modification-indicator.changed { + background: var(--color-accent); +} + +#settings .modification-indicator.changed.unsaved { + background-image: linear-gradient(var(--color-accent) 25%, var(--color-accent-soft) 75%); +} + #settings_result{ height: 1.4em; margin: 0 1.2em; From 825d0cacb87f93ced98dd59307e34f2b80e8133f Mon Sep 17 00:00:00 2001 From: Alex Heller Date: Mon, 5 Jun 2023 01:10:07 +0200 Subject: [PATCH 22/31] Changed the default value hover messages. --- javascript/ui.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/javascript/ui.js b/javascript/ui.js index 4995ad558..08bbb1ef1 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -315,7 +315,7 @@ function markIfModified(setting_name, value) { is_unsaved = !opts_metadata[setting_name].is_stored if (is_unsaved) { - elem.title = 'Unsaved value, apply settings to save.'; + elem.title = 'Default value (not saved to config)'; } elem.disabled = !(is_unsaved || changed_value) elem.classList.toggle('changed', changed_value) @@ -335,7 +335,7 @@ function markIfModified(setting_name, value) { if (changed_items.size > 0) tab_nav_indicator.title += `Click to reset ${changed_items.size} unapplied change${changed_items.size > 1 ? 's': ''} in this tab.\n` if (unsaved.size > 0) - tab_nav_indicator.title += `Apply settings to save ${unsaved.size} unsaved value${unsaved.size > 1 ? 's':''}.`; + tab_nav_indicator.title += `${unsaved.size} new default value${unsaved.size > 1 ? 's':''} (not yet saved).`; } function onSettingComponentChanged(setting_name, value) { From d182699f822780e8d121920608c881a0dfefc6ef Mon Sep 17 00:00:00 2001 From: Alex Heller Date: Mon, 5 Jun 2023 01:15:26 +0200 Subject: [PATCH 23/31] Fixed unsaved indicator not updating with "apply settings" --- javascript/ui.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ui.js b/javascript/ui.js index 08bbb1ef1..97e624307 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -192,11 +192,11 @@ function recalculate_prompts_img2img(){ opts = {} opts_metadata = {} -opts_tabs = {} function updateOpts(json_string){ let settings_data = JSON.parse(json_string) opts = settings_data.values opts_metadata = settings_data.metadata + opts_tabs = {} Object.entries(opts_metadata).forEach(([opt, meta]) => { opts_tabs[meta.tab_name] ||= {} let unsaved = (opts_tabs[meta.tab_name].unsaved_keys ||= new Set()) From bbb0aadddf459d4ba0ac5264e96cf9bfd27a0675 Mon Sep 17 00:00:00 2001 From: Alex Heller Date: Mon, 5 Jun 2023 04:26:07 +0200 Subject: [PATCH 24/31] Removed old resetting to previous values This is due to a change in `_js` which lead to the instant updates breaking :( Need to reinvestigate how to actually fix this. For now the broken feature got replaced with a working, but sloooow solution (updates one by one when resetting) --- javascript/ui.js | 10 ---------- modules/ui.py | 4 ++-- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/javascript/ui.js b/javascript/ui.js index bbfbb18fd..7ae391ec1 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -371,11 +371,6 @@ function markIfModified(setting_name, value) { tab_nav_indicator.title += `${unsaved.size} new default value${unsaved.size > 1 ? 's':''} (not yet saved).`; } -function onSettingComponentChanged(setting_name, value) { - markIfModified(setting_name, value) - return [] -} - function onSettingsModificationIndicatorClicked(tab_name) { opts_tabs[tab_name].changed.forEach(function(setting_name){ // Click each setting's modification indicator @@ -383,11 +378,6 @@ function onSettingsModificationIndicatorClicked(tab_name) { }) } -function onModificationIndicatorClicked(setting_name) { - markIfModified(setting_name, opts[setting_name]) - return JSON.parse(JSON.stringify(opts[setting_name])); -} - let txt2img_textarea; let img2img_textarea; const wait_time = 800; diff --git a/modules/ui.py b/modules/ui.py index 589089e5e..3684a5875 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1300,8 +1300,8 @@ def create_ui(): res = comp(label=info.label, value=fun(), elem_id=elem_id, **(args or {})) if not is_quicksettings: - res.change(fn=None, inputs=res, _js=f'(val) => onSettingComponentChanged("{key}", val)') - dirty_indicator.click(fn=None, outputs=res, _js=f'() => onModificationIndicatorClicked("{key}")') + res.change(fn=None, inputs=res, _js=f'(val) => markIfModified("{key}", val)') + dirty_indicator.click(fn=lambda: getattr(opts, key), outputs=res, show_progress=False) dirtyable_setting.__exit__() return res From 62e9635a5b859658e8ee1b123681d9f9e9cbf3b6 Mon Sep 17 00:00:00 2001 From: Alex Heller Date: Mon, 5 Jun 2023 04:45:43 +0200 Subject: [PATCH 25/31] Adapted setting search to new tabitem node layout --- javascript/ui.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/javascript/ui.js b/javascript/ui.js index 7ae391ec1..0507df6d5 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -260,15 +260,18 @@ onUiUpdate(() => { const settings_search = gradioApp().querySelectorAll('#settings_search > label > textarea')[0]; settings_search.oninput = (e) => { setTimeout(() => { - gradioApp().querySelectorAll('#settings > div').forEach((elem) => { + gradioApp().querySelectorAll('#settings > .tab-content > .tabitem').forEach((elem) => { if (elem.id === 'settings_tab_licenses') return; elem.style.display = 'block'; }); gradioApp().querySelectorAll('#tab_settings .tabitem').forEach((section) => { - section.querySelectorAll('.block').forEach((setting) => { + section.querySelectorAll('.dirtyable').forEach((setting) => { const visible = setting.innerText.toLowerCase().includes(e.target.value.toLowerCase()) || setting.id.toLowerCase().includes(e.target.value.toLowerCase()); - if (setting.parentElement.classList.contains('form')) setting.parentElement.style.display = visible ? 'flex' : 'none'; - else setting.style.display = visible ? 'block' : 'none'; + if (!visible) { + setting.style.display = 'none' + } else { + setting.style.removeProperty('display') + } }); }); }, 50); From f47444edc595d67c7dca0e7bf8fdbe2c503db949 Mon Sep 17 00:00:00 2001 From: Alex Heller Date: Thu, 8 Jun 2023 17:50:48 +0200 Subject: [PATCH 26/31] Removed double use of `ui` id in setting tabs --- modules/shared.py | 1744 ++++++++++++++++++++++----------------------- 1 file changed, 872 insertions(+), 872 deletions(-) diff --git a/modules/shared.py b/modules/shared.py index e34a4a5f7..028056abf 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -1,872 +1,872 @@ -import os -import sys -import time -import json -import datetime -import urllib.request -from enum import Enum -import gradio as gr -import tqdm -import requests -from modules import errors, ui_components, shared_items, cmd_args -from modules.paths_internal import models_path, script_path, data_path, sd_configs_path, sd_default_config, sd_model_file, default_sd_model_file, extensions_dir, extensions_builtin_dir # pylint: disable=W0611 -import modules.interrogate -import modules.memmon -import modules.styles -import modules.devices as devices -import modules.paths_internal as paths -from installer import log as central_logger # pylint: disable=E0611 - - -errors.install(gr) -demo: gr.Blocks = None -log = central_logger -progress_print_out = sys.stdout -parser = cmd_args.parser -url = 'https://github.com/vladmandic/automatic' -cmd_opts, _ = parser.parse_known_args() -hide_dirs = {"visible": not cmd_opts.hide_ui_dir_config} -xformers_available = False -clip_model = None -interrogator = modules.interrogate.InterrogateModels("interrogate") -sd_upscalers = [] -face_restorers = [] -tab_names = [] -options_templates = {} -hypernetworks = {} -loaded_hypernetworks = [] -gradio_theme = gr.themes.Base() -settings_components = None -latent_upscale_default_mode = "Latent" -latent_upscale_modes = { - "Latent": {"mode": "bilinear", "antialias": False}, - "Latent (antialiased)": {"mode": "bilinear", "antialias": True}, - "Latent (bicubic)": {"mode": "bicubic", "antialias": False}, - "Latent (bicubic antialiased)": {"mode": "bicubic", "antialias": True}, - "Latent (nearest)": {"mode": "nearest", "antialias": False}, - "Latent (nearest-exact)": {"mode": "nearest-exact", "antialias": False}, -} -restricted_opts = { - "samples_filename_pattern", - "directories_filename_pattern", - "outdir_samples", - "outdir_txt2img_samples", - "outdir_img2img_samples", - "outdir_extras_samples", - "outdir_grids", - "outdir_txt2img_grids", - "outdir_save", - "outdir_init_images" -} -ui_reorder_categories = [ - "inpaint", - "sampler", - "checkboxes", - "hires_fix", - "dimensions", - "cfg", - "seed", - "batch", - "override_settings", - "scripts", -] - - -class Backend(Enum): - ORIGINAL = 1 - DIFFUSERS = 2 - - -def reload_hypernetworks(): - from modules.hypernetworks import hypernetwork - global hypernetworks # pylint: disable=W0603 - hypernetworks = hypernetwork.list_hypernetworks(opts.hypernetwork_dir) - - -class State: - skipped = False - interrupted = False - paused = False - job = "" - job_no = 0 - job_count = 0 - processing_has_refined_job_count = False - job_timestamp = '0' - sampling_step = 0 - sampling_steps = 0 - current_latent = None - current_image = None - current_image_sampling_step = 0 - id_live_preview = 0 - textinfo = None - time_start = None - need_restart = False - server_start = None - - def skip(self): - log.debug('Requested skip') - self.skipped = True - - def interrupt(self): - log.debug('Requested interrupt') - self.interrupted = True - - def pause(self): - self.paused = not self.paused - log.debug(f'Requested {"pause" if self.paused else "continue"}') - - def nextjob(self): - if opts.live_previews_enable and opts.show_progress_every_n_steps == -1: - self.do_set_current_image() - self.job_no += 1 - self.sampling_step = 0 - self.current_image_sampling_step = 0 - - def dict(self): - obj = { - "skipped": self.skipped, - "interrupted": self.interrupted, - "job": self.job, - "job_count": self.job_count, - "job_timestamp": self.job_timestamp, - "job_no": self.job_no, - "sampling_step": self.sampling_step, - "sampling_steps": self.sampling_steps, - } - return obj - - def begin(self): - self.sampling_step = 0 - self.job_count = -1 - self.processing_has_refined_job_count = False - self.job_no = 0 - self.job_timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S") - self.current_latent = None - self.current_image = None - self.current_image_sampling_step = 0 - self.id_live_preview = 0 - self.skipped = False - self.interrupted = False - self.paused = False - self.textinfo = None - self.time_start = time.time() - devices.torch_gc() - - def end(self): - self.job = "" - self.job_count = 0 - self.paused = False - devices.torch_gc() - - def set_current_image(self): - """sets self.current_image from self.current_latent if enough sampling steps have been made after the last call to this""" - if not parallel_processing_allowed: - return - if self.sampling_step - self.current_image_sampling_step >= opts.show_progress_every_n_steps and opts.live_previews_enable and opts.show_progress_every_n_steps != -1: - self.do_set_current_image() - - def do_set_current_image(self): - if self.current_latent is None: - return - import modules.sd_samplers # pylint: disable=W0621 - if opts.show_progress_grid: - self.assign_current_image(modules.sd_samplers.samples_to_image_grid(self.current_latent)) - else: - self.assign_current_image(modules.sd_samplers.sample_to_image(self.current_latent)) - self.current_image_sampling_step = self.sampling_step - - def assign_current_image(self, image): - self.current_image = image - self.id_live_preview += 1 - -state = State() -state.server_start = time.time() - - -class OptionInfo: - def __init__(self, default=None, label="", component=None, component_args=None, onchange=None, section=None, refresh=None, comment_before='', comment_after=''): - self.default = default - self.label = label - self.component = component - self.component_args = component_args - self.onchange = onchange - self.section = section - self.refresh = refresh - self.comment_before = comment_before # HTML text that will be added after label in UI - self.comment_after = comment_after # HTML text that will be added before label in UI - - def link(self, label, uri): - self.comment_before += f"[{label}]" - return self - - def js(self, label, js_func): - self.comment_before += f"[{label}]" - return self - - def info(self, info): - self.comment_after += f"({info})" - return self - - def needs_restart(self): - self.comment_after += " (requires restart)" - return self - - -def options_section(section_identifier, options_dict): - for v in options_dict.values(): - v.section = section_identifier - return options_dict - - -def list_checkpoint_tiles(): - import modules.sd_models # pylint: disable=W0621 - return modules.sd_models.checkpoint_tiles() - - -default_checkpoint = list_checkpoint_tiles()[0] if len(list_checkpoint_tiles()) > 0 else "model.ckpt" - - -def refresh_checkpoints(): - import modules.sd_models # pylint: disable=W0621 - return modules.sd_models.list_models() - - -def list_samplers(): - import modules.sd_samplers # pylint: disable=W0621 - modules.sd_samplers.set_samplers() - return modules.sd_samplers.all_samplers - -def list_themes(): - fn = os.path.join('html', 'themes.json') - if not os.path.exists(fn): - refresh_themes() - if os.path.exists(fn): - with open(fn, mode='r', encoding='utf=8') as f: - res = json.loads(f.read()) - else: - res = [] - builtin = ["black-orange", "gradio/default", "gradio/base", "gradio/glass", "gradio/monochrome", "gradio/soft"] - themes = sorted(set(builtin + [x['id'] for x in res if x['status'] == 'RUNNING' and 'test' not in x['id'].lower()]), key=str.casefold) - return themes - - -def refresh_themes(): - try: - req = requests.get('https://huggingface.co/datasets/freddyaboulton/gradio-theme-subdomains/resolve/main/subdomains.json', timeout=5) - if req.status_code == 200: - res = req.json() - fn = os.path.join('html', 'themes.json') - with open(fn, mode='w', encoding='utf=8') as f: - f.write(json.dumps(res)) - else: - log.error('Error refreshing UI themes') - except: - log.error('Exception refreshing UI themes') - - -if devices.backend == "cpu": - cross_attention_optimization_default = "Doggettx's" -elif devices.backend == "mps": - cross_attention_optimization_default = "Doggettx's" -elif devices.backend == "ipex": - cross_attention_optimization_default = "InvokeAI's" -elif devices.backend == "directml": - cross_attention_optimization_default = "Sub-quadratic" -elif devices.backend == "rocm": - cross_attention_optimization_default = "Sub-quadratic" -else: # cuda - cross_attention_optimization_default ="Scaled-Dot-Product" - -options_templates.update(options_section(('sd', "Stable Diffusion"), { - "sd_model_checkpoint": OptionInfo(default_checkpoint, "Stable Diffusion checkpoint", gr.Dropdown, lambda: {"choices": list_checkpoint_tiles()}, refresh=refresh_checkpoints), - "sd_checkpoint_cache": OptionInfo(0, "Number of cached model checkpoints", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), - "sd_vae_checkpoint_cache": OptionInfo(0, "Number of cached VAE checkpoints", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), - "sd_vae": OptionInfo("Automatic", "Select VAE", gr.Dropdown, lambda: {"choices": shared_items.sd_vae_items()}, refresh=shared_items.refresh_vae_list), - "sd_vae_sliced_encode": OptionInfo(False, "Enable splitting of hires batch processing"), - "stream_load": OptionInfo(False, "When loading models attempt stream loading optimized for slow or network storage"), - "model_reuse_dict": OptionInfo(False, "When loading models attempt to reuse previous model dictionary"), - "cross_attention_optimization": OptionInfo(cross_attention_optimization_default, "Cross-attention optimization method", gr.Radio, lambda: {"choices": shared_items.list_crossattention() }), - "cross_attention_options": OptionInfo([], "Cross-attention advanced options", gr.CheckboxGroup, lambda: {"choices": ['xFormers enable flash Attention', 'SDP disable memory attention']}), - "sub_quad_q_chunk_size": OptionInfo(512, "Sub-quadratic cross-attention query chunk size", gr.Slider, {"minimum": 16, "maximum": 8192, "step": 8}), - "sub_quad_kv_chunk_size": OptionInfo(512, "Sub-quadratic cross-attention kv chunk size", gr.Slider, {"minimum": 0, "maximum": 8192, "step": 8}), - "sub_quad_chunk_threshold": OptionInfo(80, "Sub-quadratic cross-attention chunking threshold", gr.Slider, {"minimum": 0, "maximum": 100, "step": 1}), - "prompt_attention": OptionInfo("Full parser", "Prompt attention parser", gr.Radio, lambda: {"choices": ["Full parser", "Compel parser", "A1111 parser", "Fixed attention"] }), - "prompt_mean_norm": OptionInfo(True, "Prompt attention mean normalization"), - "always_batch_cond_uncond": OptionInfo(False, "Disable conditional batching enabled on low memory systems"), - "enable_quantization": OptionInfo(True, "Enable samplers quantization for sharper and cleaner results"), - "comma_padding_backtrack": OptionInfo(20, "Increase coherency by padding from the last comma within n tokens when using more than 75 tokens", gr.Slider, {"minimum": 0, "maximum": 74, "step": 1 }), - "sd_backend": OptionInfo("Original", "Stable Diffusion backend (experimental)", gr.Radio, lambda: {"choices": ["Original", "Diffusers"] }), -})) - -options_templates.update(options_section(('cuda', "Compute Settings"), { - "memmon_poll_rate": OptionInfo(2, "VRAM usage polls per second during generation", gr.Slider, {"minimum": 0, "maximum": 40, "step": 1}), - "precision": OptionInfo("Autocast", "Precision type", gr.Radio, lambda: {"choices": ["Autocast", "Full"]}), - "cuda_dtype": OptionInfo("FP32" if sys.platform == "darwin" else "FP16", "Device precision type", gr.Radio, lambda: {"choices": ["FP32", "FP16", "BF16"]}), - "no_half": OptionInfo(False, "Use full precision for model (--no-half)", None, None, None), - "no_half_vae": OptionInfo(False, "Use full precision for VAE (--no-half-vae)"), - "upcast_sampling": OptionInfo(True if sys.platform == "darwin" else False, "Enable upcast sampling"), - "upcast_attn": OptionInfo(False, "Enable upcast cross attention layer"), - "disable_nan_check": OptionInfo(True, "Disable NaN check in produced images/latent spaces"), - "rollback_vae": OptionInfo(False, "Attempt to roll back VAE when produced NaN values, requires NaN check (experimental)"), - "opt_channelslast": OptionInfo(False, "Use channels last as torch memory format "), - "cudnn_benchmark": OptionInfo(False, "Enable full-depth cuDNN benchmark feature"), - "cuda_allow_tf32": OptionInfo(True, "Allow TF32 math ops"), - "cuda_allow_tf16_reduced": OptionInfo(True, "Allow TF16 reduced precision math ops"), - "cuda_compile": OptionInfo(False, "Enable model compile (experimental)"), - "cuda_compile_mode": OptionInfo("none", "Model compile mode (experimental)", gr.Radio, lambda: {"choices": ['none', 'inductor', 'cudagraphs', 'aot_ts_nvfuser', 'hidet', 'ipex']}), - "cuda_compile_verbose": OptionInfo(False, "Model compile verbose mode"), - "cuda_compile_errors": OptionInfo(True, "Model compile suppress errors"), - "disable_gc": OptionInfo(False, "Disable Torch memory garbage collection (experimental)"), -})) - -options_templates.update(options_section(('system-paths', "System Paths"), { - "temp_dir": OptionInfo("", "Directory for temporary images; leave empty for default"), - "clean_temp_dir_at_start": OptionInfo(True, "Cleanup non-default temporary directory when starting webui"), - "ckpt_dir": OptionInfo(os.path.join(paths.models_path, 'Stable-diffusion'), "Path to directory with stable diffusion checkpoints"), - "diffusers_dir": OptionInfo(os.path.join(paths.models_path, 'Diffusers'), "Path to directory with stable diffusion diffusers"), - "vae_dir": OptionInfo(os.path.join(paths.models_path, 'VAE'), "Path to directory with VAE files"), - "embeddings_dir": OptionInfo(os.path.join(paths.models_path, 'embeddings'), "Embeddings directory for textual inversion"), - "hypernetwork_dir": OptionInfo(os.path.join(paths.models_path, 'hypernetworks'), "Hypernetwork directory"), - "codeformer_models_path": OptionInfo(os.path.join(paths.models_path, 'Codeformer'), "Path to directory with codeformer model file(s)"), - "gfpgan_models_path": OptionInfo(os.path.join(paths.models_path, 'GFPGAN'), "Path to directory with GFPGAN model file(s)"), - "esrgan_models_path": OptionInfo(os.path.join(paths.models_path, 'ESRGAN'), "Path to directory with ESRGAN model file(s)"), - "bsrgan_models_path": OptionInfo(os.path.join(paths.models_path, 'BSRGAN'), "Path to directory with BSRGAN model file(s)"), - "realesrgan_models_path": OptionInfo(os.path.join(paths.models_path, 'RealESRGAN'), "Path to directory with RealESRGAN model file(s)"), - "scunet_models_path": OptionInfo(os.path.join(paths.models_path, 'ScuNET'), "Path to directory with ScuNET model file(s)"), - "swinir_models_path": OptionInfo(os.path.join(paths.models_path, 'SwinIR'), "Path to directory with SwinIR model file(s)"), - "ldsr_models_path": OptionInfo(os.path.join(paths.models_path, 'LDSR'), "Path to directory with LDSR model file(s)"), - "clip_models_path": OptionInfo(os.path.join(paths.models_path, 'CLIP'), "Path to directory with CLIP model file(s)"), - "lora_dir": OptionInfo(os.path.join(paths.models_path, 'Lora'), "Path to directory with Lora network(s)"), - "lyco_dir": OptionInfo(os.path.join(paths.models_path, 'LyCORIS'), "Path to directory with LyCORIS network(s)"), - "styles_dir": OptionInfo(os.path.join(paths.data_path, 'styles.csv'), "Path to user-defined styles file"), -})) - -options_templates.update(options_section(('saving-images', "Image Options"), { - "samples_save": OptionInfo(True, "Always save all generated images"), - "samples_format": OptionInfo('jpg', 'File format for generated images', gr.Dropdown, lambda: {"choices": ["jpg", "png", "webp", "tiff", "jp2"]}), - "samples_filename_pattern": OptionInfo("[seed]-[prompt_spaces]", "Images filename pattern", component_args=hide_dirs), - "save_images_add_number": OptionInfo(True, "Add number to filename when saving", component_args=hide_dirs), - "grid_save": OptionInfo(True, "Always save all generated image grids"), - "grid_format": OptionInfo('jpg', 'File format for grids', gr.Dropdown, lambda: {"choices": ["jpg", "png", "webp", "tiff", "jp2"]}), - "grid_extended_filename": OptionInfo(True, "Add extended info (seed, prompt) to filename when saving grid"), - "grid_only_if_multiple": OptionInfo(True, "Do not save grids consisting of one picture"), - "grid_prevent_empty_spots": OptionInfo(True, "Prevent empty spots in grid (when set to autodetect)"), - "n_rows": OptionInfo(-1, "Grid row count; use -1 for autodetect and 0 for it to be same as batch size", gr.Slider, {"minimum": -1, "maximum": 16, "step": 1}), - "save_txt": OptionInfo(False, "Create a text file next to every image with generation parameters"), - "save_log_fn": OptionInfo("", "Create a JSON log file with image information for each saved image", component_args=hide_dirs), - "save_images_before_face_restoration": OptionInfo(False, "Save a copy of image before doing face restoration"), - "save_images_before_highres_fix": OptionInfo(False, "Save a copy of image before applying highres fix"), - "save_images_before_color_correction": OptionInfo(False, "Save a copy of image before applying color correction to img2img results"), - "save_mask": OptionInfo(False, "Save a copy of the inpainting greyscale mask"), - "save_mask_composite": OptionInfo(False, "Save a copy of inpainting masked composite"), - "save_init_img": OptionInfo(False, "Save a copy of processing init images"), - "jpeg_quality": OptionInfo(85, "Quality for saved jpeg images", gr.Slider, {"minimum": 1, "maximum": 100, "step": 1}), - "webp_lossless": OptionInfo(False, "Use lossless compression for webp images"), - "img_max_size_mp": OptionInfo(250, "Maximum allowed image size in megapixels", gr.Number), - "use_original_name_batch": OptionInfo(True, "Use original name for output filename during batch process in extras tab"), - "use_upscaler_name_as_suffix": OptionInfo(True, "Use upscaler name as filename suffix in the extras tab"), - "save_selected_only": OptionInfo(True, "When using 'Save' button, only save a single selected image"), - "save_to_dirs": OptionInfo(False, "Save images to a subdirectory"), - "grid_save_to_dirs": OptionInfo(False, "Save grids to a subdirectory"), - "use_save_to_dirs_for_ui": OptionInfo(False, "Save images to a subdirectory when using Save button"), - "directories_filename_pattern": OptionInfo("[date]", "Directory name pattern", component_args=hide_dirs), - "directories_max_prompt_words": OptionInfo(8, "Max prompt words for [prompt_words] pattern", gr.Slider, {"minimum": 1, "maximum": 20, "step": 1, **hide_dirs}), -})) - -options_templates.update(options_section(('image-processing', "Image Processing"), { - "img2img_color_correction": OptionInfo(False, "Apply color correction to match original colors"), - "img2img_fix_steps": OptionInfo(False, "For image processing do exact number of steps as specified"), - "img2img_background_color": OptionInfo("#ffffff", "Image transparent color fill", ui_components.FormColorPicker, {}), - "inpainting_mask_weight": OptionInfo(1.0, "Inpainting conditioning mask strength", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), - "initial_noise_multiplier": OptionInfo(1.0, "Noise multiplier for image processing", gr.Slider, {"minimum": 0.1, "maximum": 1.5, "step": 0.01}), - "CLIP_stop_at_last_layers": OptionInfo(1, "Clip skip", gr.Slider, {"minimum": 1, "maximum": 8, "step": 1, "visible": False}), -})) - - -options_templates.update(options_section(('saving-paths', "Output Paths"), { - "outdir_samples": OptionInfo("", "Output directory for images; if empty, defaults to three directories below", component_args=hide_dirs), - "outdir_txt2img_samples": OptionInfo("outputs/text", 'Output directory for txt2img images', component_args=hide_dirs), - "outdir_img2img_samples": OptionInfo("outputs/image", 'Output directory for img2img images', component_args=hide_dirs), - "outdir_extras_samples": OptionInfo("outputs/extras", 'Output directory for images from extras tab', component_args=hide_dirs), - "outdir_grids": OptionInfo("", "Output directory for grids; if empty, defaults to two directories below", component_args=hide_dirs), - "outdir_txt2img_grids": OptionInfo("outputs/grids", 'Output directory for txt2img grids', component_args=hide_dirs), - "outdir_img2img_grids": OptionInfo("outputs/grids", 'Output directory for img2img grids', component_args=hide_dirs), - "outdir_save": OptionInfo("outputs/save", "Directory for saving images using the Save button", component_args=hide_dirs), - "outdir_init_images": OptionInfo("outputs/init-images", "Directory for saving init images when using img2img", component_args=hide_dirs), -})) - -options_templates.update(options_section(('ui', "User interface"), { - "gradio_theme": OptionInfo("black-orange", "UI theme", gr.Dropdown, lambda: {"choices": list_themes()}, refresh=refresh_themes), - "theme_style": OptionInfo("Auto", "Theme mode", gr.Radio, {"choices": ["Auto", "Dark", "Light"]}), - "return_grid": OptionInfo(True, "Show grid in results for web"), - "return_mask": OptionInfo(False, "For inpainting, include the greyscale mask in results for web"), - "return_mask_composite": OptionInfo(False, "For inpainting, include masked composite in results for web"), - "disable_weights_auto_swap": OptionInfo(True, "Do not change the selected model when reading generation parameters"), - "send_seed": OptionInfo(True, "Send seed when sending prompt or image to other interface"), - "send_size": OptionInfo(True, "Send size when sending prompt or image to another interface"), - "font": OptionInfo("", "Font for image grids that have text"), - "keyedit_precision_attention": OptionInfo(0.1, "Ctrl+up/down precision when editing (attention:1.1)", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}), - "keyedit_precision_extra": OptionInfo(0.05, "Ctrl+up/down precision when editing ", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}), - "keyedit_delimiters": OptionInfo(".,\/!?%^*;:{}=`~()", "Ctrl+up/down word delimiters"), # pylint: disable=anomalous-backslash-in-string - "quicksettings_list": OptionInfo(["sd_model_checkpoint"], "Quicksettings list", ui_components.DropdownMulti, lambda: {"choices": list(opts.data_labels.keys())}), - "hidden_tabs": OptionInfo([], "Hidden UI tabs", ui_components.DropdownMulti, lambda: {"choices": [x for x in tab_names]}), - "ui_tab_reorder": OptionInfo("From Text, From Image, Process Image", "UI tabs order"), - "ui_scripts_reorder": OptionInfo("Enable Dynamic Thresholding, ControlNet", "UI scripts order"), - "ui_reorder": OptionInfo(", ".join(ui_reorder_categories), "txt2img/img2img UI item order"), - "ui_extra_networks_tab_reorder": OptionInfo("Checkpoints, Lora, LyCORIS, Textual Inversion, Hypernetworks", "Extra networks tab order"), -})) - -options_templates.update(options_section(('ui', "Live previews"), { - "show_progressbar": OptionInfo(True, "Show progressbar"), - "live_previews_enable": OptionInfo(True, "Show live previews of the created image"), - "show_progress_grid": OptionInfo(True, "Show previews of all images generated in a batch as a grid"), - "notification_audio_enable": OptionInfo(False, "Play a sound when images are finished generating"), - "notification_audio_path": OptionInfo("html/notification.mp3","Path to notification sound", component_args=hide_dirs), - "show_progress_every_n_steps": OptionInfo(1, "Live preview display period", gr.Slider, {"minimum": -1, "maximum": 32, "step": 1}), - "show_progress_type": OptionInfo("TAESD", "Live preview method", gr.Radio, {"choices": ["Full", "Approx NN", "Approx cheap", "TAESD"]}), - "live_preview_content": OptionInfo("Combined", "Live preview subject", gr.Radio, {"choices": ["Combined", "Prompt", "Negative prompt"]}), - "live_preview_refresh_period": OptionInfo(250, "Progressbar/preview update period, in milliseconds") -})) - -options_templates.update(options_section(('sampler-params', "Sampler Settings"), { - "show_samplers": OptionInfo(["Euler a", "UniPC", "DDIM", "DPM++ 2M SDE", "DPM++ 2M SDE Karras", "DPM2 Karras", "DPM++ 2M Karras"], "Show samplers in user interface", gr.CheckboxGroup, lambda: {"choices": [x.name for x in list_samplers() if x.name != "PLMS"]}), - "fallback_sampler": OptionInfo("Euler a", "Secondary sampler", gr.Dropdown, lambda: {"choices": ["None"] + [x.name for x in list_samplers()]}), - "xyz_fallback_sampler": OptionInfo("None", "Force latent upscaler sampler", gr.Dropdown, lambda: {"choices": ["None"] + [x.name for x in list_samplers()]}), - "eta_ancestral": OptionInfo(1.0, "Noise multiplier for ancestral samplers (eta)", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), - "eta_ddim": OptionInfo(0.0, "Noise multiplier for DDIM (eta)", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), - "ddim_discretize": OptionInfo('uniform', "DDIM discretize img2img", gr.Radio, {"choices": ['uniform', 'quad']}), - 's_churn': OptionInfo(0.0, "sigma churn", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), - 's_min_uncond': OptionInfo(0, "Negative Guidance minimum sigma", gr.Slider, {"minimum": 0.0, "maximum": 4.0, "step": 0.01}), - 's_tmin': OptionInfo(0.0, "sigma tmin", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), - 's_noise': OptionInfo(1.0, "sigma noise", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), - 'eta_noise_seed_delta': OptionInfo(0, "Noise seed delta (eta)", gr.Number, {"precision": 0}), - 'always_discard_next_to_last_sigma': OptionInfo(False, "Always discard next-to-last sigma"), - 'uni_pc_variant': OptionInfo("bh1", "UniPC variant", gr.Radio, {"choices": ["bh1", "bh2", "vary_coeff"]}), - 'uni_pc_skip_type': OptionInfo("time_uniform", "UniPC skip type", gr.Radio, {"choices": ["time_uniform", "time_quadratic", "logSNR"]}), - 'uni_pc_order': OptionInfo(3, "UniPC order (must be < sampling steps)", gr.Slider, {"minimum": 1, "maximum": 50, "step": 1}), - 'uni_pc_lower_order_final': OptionInfo(True, "UniPC lower order final"), -})) - -options_templates.update(options_section(('postprocessing', "Postprocessing"), { - 'postprocessing_enable_in_main_ui': OptionInfo([], "Enable addtional postprocessing operations", ui_components.DropdownMulti, lambda: {"choices": [x.name for x in shared_items.postprocessing_scripts()]}), - 'postprocessing_operation_order': OptionInfo([], "Postprocessing operation order", ui_components.DropdownMulti, lambda: {"choices": [x.name for x in shared_items.postprocessing_scripts()]}), - 'upscaling_max_images_in_cache': OptionInfo(5, "Maximum number of images in upscaling cache", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), -})) - -options_templates.update(options_section(('training', "Training"), { - "unload_models_when_training": OptionInfo(False, "Move VAE and CLIP to RAM when training if possible"), - "pin_memory": OptionInfo(True, "Pin training dataset to memory"), - "save_optimizer_state": OptionInfo(False, "Saves resumable optimizer state when training embedding or hypernetwork"), - "save_training_settings_to_txt": OptionInfo(True, "Save textual inversion and hypernet settings to a text file whenever training starts"), - "dataset_filename_word_regex": OptionInfo("", "Filename word regex"), - "dataset_filename_join_string": OptionInfo(" ", "Filename join string"), - "embeddings_templates_dir": OptionInfo(os.path.join(paths.script_path, 'train', 'templates'), "Embeddings train templates directory"), - "training_image_repeats_per_epoch": OptionInfo(1, "Number of repeats for a single input image per epoch; used only for displaying epoch number", gr.Number, {"precision": 0}), - "training_write_csv_every": OptionInfo(0, "Save an csv containing the loss to log directory every N steps, 0 to disable"), - "training_enable_tensorboard": OptionInfo(False, "Enable tensorboard logging"), - "training_tensorboard_save_images": OptionInfo(False, "Save generated images within tensorboard"), - "training_tensorboard_flush_every": OptionInfo(120, "How often, in seconds, to flush the pending tensorboard events and summaries to disk"), -})) - -options_templates.update(options_section(('interrogate', "Interrogate"), { - "interrogate_keep_models_in_memory": OptionInfo(False, "Interrogate: keep models in VRAM"), - "interrogate_return_ranks": OptionInfo(True, "Interrogate: include ranks of model tags matches in results"), - "interrogate_clip_num_beams": OptionInfo(1, "Interrogate: num_beams for BLIP", gr.Slider, {"minimum": 1, "maximum": 16, "step": 1}), - "interrogate_clip_min_length": OptionInfo(32, "Interrogate: minimum description length (excluding artists, etc..)", gr.Slider, {"minimum": 1, "maximum": 128, "step": 1}), - "interrogate_clip_max_length": OptionInfo(192, "Interrogate: maximum description length", gr.Slider, {"minimum": 1, "maximum": 256, "step": 1}), - "interrogate_clip_dict_limit": OptionInfo(2048, "CLIP: maximum number of lines in text file (0 = No limit)"), - "interrogate_clip_skip_categories": OptionInfo(["artists", "movements", "flavors"], "CLIP: skip inquire categories", gr.CheckboxGroup, lambda: {"choices": modules.interrogate.category_types()}, refresh=modules.interrogate.category_types), - "interrogate_deepbooru_score_threshold": OptionInfo(0.65, "Interrogate: deepbooru score threshold", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01}), - "deepbooru_sort_alpha": OptionInfo(False, "Interrogate: deepbooru sort alphabetically"), - "deepbooru_use_spaces": OptionInfo(False, "use spaces for tags in deepbooru"), - "deepbooru_escape": OptionInfo(True, "escape (\\) brackets in deepbooru (so they are used as literal brackets and not for emphasis)"), - "deepbooru_filter_tags": OptionInfo("", "filter out those tags from deepbooru output (separated by comma)"), -})) - -options_templates.update(options_section(('upscaling', "Upscaling"), { - "upscaler_for_img2img": OptionInfo("None", "Default upscaler for image resize operations", gr.Dropdown, lambda: {"choices": [x.name for x in sd_upscalers]}), - "realesrgan_enabled_models": OptionInfo(["R-ESRGAN 4x+", "R-ESRGAN 4x+ Anime6B"], "Real-ESRGAN available models", gr.CheckboxGroup, lambda: {"choices": shared_items.realesrgan_models_names()}), - "ESRGAN_tile": OptionInfo(192, "Tile size for ESRGAN upscalers (0 = no tiling)", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}), - "ESRGAN_tile_overlap": OptionInfo(8, "Tile overlap in pixels for ESRGAN upscalers", gr.Slider, {"minimum": 0, "maximum": 48, "step": 1}), - "SCUNET_tile": OptionInfo(256, "Tile size for SCUNET upscalers. 0 = no tiling.", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}), - "SCUNET_tile_overlap": OptionInfo(8, "Tile overlap, in pixels for SCUNET upscalers. Low values = visible seam.", gr.Slider, {"minimum": 0, "maximum": 64, "step": 1}), - "use_old_hires_fix_width_height": OptionInfo(False, "Hires fix uses width & height to set final resolution rather than first pass"), - "dont_fix_second_order_samplers_schedule": OptionInfo(False, "Do not fix prompt schedule for second order samplers"), - "lyco_patch_lora": OptionInfo(False, "Use LyCoris handler for all Lora types", gr.Checkbox, { "visible": False }), # TODO: lyco-patch-lora - "lora_functional": OptionInfo(False, "Use Kohya method for handling multiple Loras", gr.Checkbox, { "visible": False }), -})) - -# options_templates.update(options_section(('lora', "Lora"), { -# })) - -options_templates.update(options_section(('face-restoration', "Face restoration"), { - "face_restoration_model": OptionInfo("CodeFormer", "Face restoration model", gr.Radio, lambda: {"choices": [x.name() for x in face_restorers]}), - "code_former_weight": OptionInfo(0.2, "CodeFormer weight parameter; 0 = maximum effect; 1 = minimum effect", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01}), - "face_restoration_unload": OptionInfo(False, "Move face restoration model from VRAM into RAM after processing"), -})) - -options_templates.update(options_section(('extra_networks', "Extra Networks"), { - "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"), - "sd_hypernetwork": OptionInfo("None", "Add hypernetwork to prompt", gr.Dropdown, lambda: {"choices": ["None"] + [x for x in hypernetworks.keys()]}, refresh=reload_hypernetworks), -})) - -options_templates.update(options_section(('token_merging', 'Token Merging'), { - "token_merging": OptionInfo(False, "Enable redundant token merging via tomesd for speed and memory improvements", gr.Checkbox), - "token_merging_ratio": OptionInfo(0.5, "Token merging Ratio. Higher merging ratio = faster generation, smaller VRAM usage, lower quality.", gr.Slider, {"minimum": 0, "maximum": 0.9, "step": 0.1}), - "token_merging_hr_only": OptionInfo(True, "Apply only to high-res fix pass. Disabling can yield a ~20-35% speedup on contemporary resolutions.", gr.Checkbox), - "token_merging_ratio_hr": OptionInfo(0.5, "Merging Ratio (high-res pass) - If 'Apply only to high-res' is enabled, this will always be the ratio used.", gr.Slider, {"minimum": 0, "maximum": 0.9, "step": 0.1}), - "token_merging_random": OptionInfo(False, "Use random perturbations - Can improve outputs for certain samplers. For others, it may cause visual artifacting.", gr.Checkbox), - "token_merging_merge_attention": OptionInfo(True, "Merge attention (Recommend on)", gr.Checkbox), - "token_merging_merge_cross_attention": OptionInfo(False, "Merge cross attention (Recommend off)", gr.Checkbox), - "token_merging_merge_mlp": OptionInfo(False, "Merge mlp (Strongly recommend off)", gr.Checkbox), - "token_merging_maximum_down_sampling": OptionInfo(1, "Maximum down sampling", gr.Radio, lambda: {"choices": [1, 2, 4, 8]}), - "token_merging_stride_x": OptionInfo(2, "Stride - X", gr.Slider, {"minimum": 2, "maximum": 8, "step": 2}), - "token_merging_stride_y": OptionInfo(2, "Stride - Y", gr.Slider, {"minimum": 2, "maximum": 8, "step": 2}) -})) - -options_templates.update(options_section((None, "Hidden options"), { - "disabled_extensions": OptionInfo([], "Disable these extensions"), - "disable_all_extensions": OptionInfo("none", "Disable all extensions (preserves the list of disabled extensions)", gr.Radio, {"choices": ["none", "user", "all"]}), - "sd_checkpoint_hash": OptionInfo("", "SHA256 hash of the current checkpoint"), -})) - -options_templates.update() - - -class Options: - data = None - data_labels = options_templates - typemap = {int: float} - - def __init__(self): - self.data = {k: v.default for k, v in self.data_labels.items()} - - def __setattr__(self, key, value): - if self.data is not None: - if key in self.data or key in self.data_labels: - if cmd_opts.freeze: - log.warning(f'Settings are frozen: {key}') - return - if cmd_opts.hide_ui_dir_config and key in restricted_opts: - log.warning(f'Settings key is restricted: {key}') - return - else: - self.data[key] = value - return - - return super(Options, self).__setattr__(key, value) - - def __getattr__(self, item): - if self.data is not None: - if item in self.data: - return self.data[item] - if item in self.data_labels: - return self.data_labels[item].default - return super(Options, self).__getattribute__(item) - - def set(self, key, value): - """sets an option and calls its onchange callback, returning True if the option changed and False otherwise""" - oldval = self.data.get(key, None) - if oldval == value: - return False - try: - setattr(self, key, value) - except RuntimeError: - return False - if self.data_labels[key].onchange is not None: - try: - self.data_labels[key].onchange() - except Exception as e: - errors.display(e, f"changing setting {key} to {value}") - setattr(self, key, oldval) - return False - return True - - def get_default(self, key): - """returns the default value for the key""" - data_label = self.data_labels.get(key) - if data_label is None: - return None - return data_label.default - - def save(self, filename): - if cmd_opts.freeze: - log.warning(f'Settings saving is disabled: {filename}') - return - with open(filename, "w", encoding="utf8") as file: - json.dump(self.data, file, indent=4) - - def same_type(self, x, y): - if x is None or y is None: - return True - type_x = self.typemap.get(type(x), type(x)) - type_y = self.typemap.get(type(y), type(y)) - return type_x == type_y - - def load(self, filename): - if not os.path.isfile(filename): - log.debug(f'Created default config: {filename}') - self.save(filename) - return - with open(filename, "r", encoding="utf8") as file: - self.data = json.load(file) - if self.data.get('quicksettings') is not None and self.data.get('quicksettings_list') is None: - self.data['quicksettings_list'] = [i.strip() for i in self.data.get('quicksettings').split(',')] - bad_settings = 0 - for k, v in self.data.items(): - info = self.data_labels.get(k, None) - if info is not None and not self.same_type(info.default, v): - log.error(f"Warning: bad setting value: {k}: {v} ({type(v).__name__}; expected {type(info.default).__name__})") - bad_settings += 1 - if bad_settings > 0: - log.error(f"Error: Bad settings found in {filename}") - - def onchange(self, key, func, call=True): - item = self.data_labels.get(key) - item.onchange = func - if call: - func() - - def dumpjson(self): - d = {k: self.data.get(k, self.data_labels.get(k).default) for k in self.data_labels.keys()} - metadata = { - k: { - "is_stored": k in self.data, - "tab_name": v.section[0] - } for k, v in self.data_labels.items() - } - return json.dumps({"values": d, "metadata": metadata}) - - def add_option(self, key, info): - self.data_labels[key] = info - - def reorder(self): - """reorder settings so that all items related to section always go together""" - section_ids = {} - settings_items = self.data_labels.items() - for k, item in settings_items: - if item.section not in section_ids: - section_ids[item.section] = len(section_ids) - self.data_labels = {k: v for k, v in sorted(settings_items, key=lambda x: section_ids[x[1].section])} - - def cast_value(self, key, value): - """casts an arbitrary to the same type as this setting's value with key - Example: cast_value("eta_noise_seed_delta", "12") -> returns 12 (an int rather than str) - """ - if value is None: - return None - default_value = self.data_labels[key].default - if default_value is None: - default_value = getattr(self, key, None) - if default_value is None: - return None - expected_type = type(default_value) - if expected_type == bool and value == "False": - value = False - elif expected_type == type(value): - pass - else: - value = expected_type(value) - return value - - -opts = Options() -config_filename = cmd_opts.config -opts.load(config_filename) -cmd_opts = cmd_args.compatibility_args(opts, cmd_opts) -if cmd_opts.backend == 'diffusers': - log.info('Overriding backend to Diffusers') - opts.data['sd_backend'] = 'Diffusers' -if cmd_opts.backend == 'original': - log.info('Overriding backend to Diffusers') - opts.data['sd_backend'] = 'Original' -backend = Backend.DIFFUSERS if opts.sd_backend == 'Diffusers' else Backend.ORIGINAL - -prompt_styles = modules.styles.StyleDatabase(opts.styles_dir) -cmd_opts.disable_extension_access = (cmd_opts.share or cmd_opts.listen or (cmd_opts.server_name or False)) and not cmd_opts.insecure -devices.device, devices.device_interrogate, devices.device_gfpgan, devices.device_esrgan, devices.device_codeformer = (devices.cpu if any(y in cmd_opts.use_cpu for y in [x, 'all']) else devices.get_optimal_device() for x in ['sd', 'interrogate', 'gfpgan', 'esrgan', 'codeformer']) -device = devices.device -batch_cond_uncond = opts.always_batch_cond_uncond or not (cmd_opts.lowvram or cmd_opts.medvram) -parallel_processing_allowed = not cmd_opts.lowvram and not cmd_opts.medvram -mem_mon = modules.memmon.MemUsageMonitor("MemMon", device, opts) -mem_mon.start() -if device.type == 'privateuseone': - import modules.dml # pylint: disable=ungrouped-imports - - -def reload_gradio_theme(theme_name=None): - global gradio_theme # pylint: disable=global-statement - if not theme_name: - theme_name = opts.gradio_theme - default_font_params = {} - res = 0 - try: - req = urllib.request.Request("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono", method="HEAD") - res = urllib.request.urlopen(req, timeout=3.0).status - except: - res = 0 - if res != 200: - log.info('No internet access detected, using default fonts') - default_font_params = { - 'font':['Helvetica', 'ui-sans-serif', 'system-ui', 'sans-serif'], - 'font_mono':['IBM Plex Mono', 'ui-monospace', 'Consolas', 'monospace'] - } - if theme_name == "black-orange": - gradio_theme = gr.themes.Default(**default_font_params) - elif theme_name.startswith("gradio/"): - if theme_name == "gradio/default": - gradio_theme = gr.themes.Default(**default_font_params) - if theme_name == "gradio/base": - gradio_theme = gr.themes.Base(**default_font_params) - if theme_name == "gradio/glass": - gradio_theme = gr.themes.Glass(**default_font_params) - if theme_name == "gradio/monochrome": - gradio_theme = gr.themes.Monochrome(**default_font_params) - if theme_name == "gradio/soft": - gradio_theme = gr.themes.Soft(**default_font_params) - else: - try: - gradio_theme = gr.themes.ThemeClass.from_hub(theme_name) - except: - log.error("Theme download error accessing HuggingFace") - gradio_theme = gr.themes.Default(**default_font_params) - log.info(f'Loading UI theme: name={theme_name} style={opts.theme_style}') - - -class TotalTQDM: - def __init__(self): - self._tqdm = None - - def reset(self): - self._tqdm = tqdm.tqdm( - desc="Total", - total=state.job_count * state.sampling_steps, - position=1, - ) - - def update(self): - if not opts.multiple_tqdm or cmd_opts.disable_console_progressbars: - return - if self._tqdm is None: - self.reset() - self._tqdm.update() - - def updateTotal(self, new_total): - if not opts.multiple_tqdm or cmd_opts.disable_console_progressbars: - return - if self._tqdm is None: - self.reset() - self._tqdm.total = new_total - - def clear(self): - if self._tqdm is not None: - self._tqdm.refresh() - self._tqdm.close() - self._tqdm = None - -total_tqdm = TotalTQDM() - - -def restart_server(restart=True): - if demo is None: - return - log.info('Server shutdown requested') - try: - demo.server.wants_restart = restart - demo.server.should_exit = True - demo.server.force_exit = True - demo.close(verbose=False) - demo.server.close() - demo.fns = [] - except: - pass - if restart: - log.info('Server will restart') - - -def restore_defaults(restart=True): - if os.path.exists(cmd_opts.config): - log.info('Restoring server defaults') - os.remove(cmd_opts.config) - if os.path.exists(cmd_opts.ui_config): - log.info('Restoring UI defaults') - os.remove(cmd_opts.ui_config) - restart_server(restart) - - -def listfiles(dirname): - filenames = [os.path.join(dirname, x) for x in sorted(os.listdir(dirname), key=str.lower) if not x.startswith(".")] - return [file for file in filenames if os.path.isfile(file)] - - -def walk_files(path, allowed_extensions=None): - if not os.path.exists(path): - return - if allowed_extensions is not None: - allowed_extensions = set(allowed_extensions) - for root, _dirs, files in os.walk(path, followlinks=True): - for filename in files: - if allowed_extensions is not None: - _, ext = os.path.splitext(filename) - if ext not in allowed_extensions: - continue - yield os.path.join(root, filename) - - -def html_path(filename): - return os.path.join(paths.script_path, "html", filename) - - -def html(filename): - path = html_path(filename) - if os.path.exists(path): - with open(path, encoding="utf8") as file: - return file.read() - return "" - - -def get_version(): - version = None - if version is None: - try: - import subprocess - res = subprocess.run('git log --pretty=format:"%h %ad" -1 --date=short', stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell=True, check=True) - ver = res.stdout.decode(encoding = 'utf8', errors='ignore') if len(res.stdout) > 0 else ' ' - githash, updated = ver.split(' ') - res = subprocess.run('git remote get-url origin', stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell=True, check=True) - origin = res.stdout.decode(encoding = 'utf8', errors='ignore') if len(res.stdout) > 0 else '' - res = subprocess.run('git branch --show-current', stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell=True, check=True) - branch = res.stdout.decode(encoding = 'utf8', errors='ignore') if len(res.stdout) > 0 else '' - version = { - 'app': 'sd.next', - 'updated': updated, - 'hash': githash, - 'url': origin.replace('\n', '') + '/tree/' + branch.replace('\n', '') - } - except: - version = { 'app': 'sd.next' } - return version - - -class Shared(sys.modules[__name__].__class__): - # this class is here to provide sd_model field as a property, so that it can be created and loaded on demand rather than at program startup. - sd_model_val = None - - @property - def sd_model(self): - import modules.sd_models # pylint: disable=W0621 - # return modules.sd_models.model_data.sd_model - return modules.sd_models.model_data.get_sd_model() - - @sd_model.setter - def sd_model(self, value): - import modules.sd_models # pylint: disable=W0621 - modules.sd_models.model_data.set_sd_model(value) - -# sd_model: LatentDiffusion = None # this var is here just for IDE's type checking; it cannot be accessed because the class field above will be accessed instead -sd_model = None -sys.modules[__name__].__class__ = Shared +import os +import sys +import time +import json +import datetime +import urllib.request +from enum import Enum +import gradio as gr +import tqdm +import requests +from modules import errors, ui_components, shared_items, cmd_args +from modules.paths_internal import models_path, script_path, data_path, sd_configs_path, sd_default_config, sd_model_file, default_sd_model_file, extensions_dir, extensions_builtin_dir # pylint: disable=W0611 +import modules.interrogate +import modules.memmon +import modules.styles +import modules.devices as devices +import modules.paths_internal as paths +from installer import log as central_logger # pylint: disable=E0611 + + +errors.install(gr) +demo: gr.Blocks = None +log = central_logger +progress_print_out = sys.stdout +parser = cmd_args.parser +url = 'https://github.com/vladmandic/automatic' +cmd_opts, _ = parser.parse_known_args() +hide_dirs = {"visible": not cmd_opts.hide_ui_dir_config} +xformers_available = False +clip_model = None +interrogator = modules.interrogate.InterrogateModels("interrogate") +sd_upscalers = [] +face_restorers = [] +tab_names = [] +options_templates = {} +hypernetworks = {} +loaded_hypernetworks = [] +gradio_theme = gr.themes.Base() +settings_components = None +latent_upscale_default_mode = "Latent" +latent_upscale_modes = { + "Latent": {"mode": "bilinear", "antialias": False}, + "Latent (antialiased)": {"mode": "bilinear", "antialias": True}, + "Latent (bicubic)": {"mode": "bicubic", "antialias": False}, + "Latent (bicubic antialiased)": {"mode": "bicubic", "antialias": True}, + "Latent (nearest)": {"mode": "nearest", "antialias": False}, + "Latent (nearest-exact)": {"mode": "nearest-exact", "antialias": False}, +} +restricted_opts = { + "samples_filename_pattern", + "directories_filename_pattern", + "outdir_samples", + "outdir_txt2img_samples", + "outdir_img2img_samples", + "outdir_extras_samples", + "outdir_grids", + "outdir_txt2img_grids", + "outdir_save", + "outdir_init_images" +} +ui_reorder_categories = [ + "inpaint", + "sampler", + "checkboxes", + "hires_fix", + "dimensions", + "cfg", + "seed", + "batch", + "override_settings", + "scripts", +] + + +class Backend(Enum): + ORIGINAL = 1 + DIFFUSERS = 2 + + +def reload_hypernetworks(): + from modules.hypernetworks import hypernetwork + global hypernetworks # pylint: disable=W0603 + hypernetworks = hypernetwork.list_hypernetworks(opts.hypernetwork_dir) + + +class State: + skipped = False + interrupted = False + paused = False + job = "" + job_no = 0 + job_count = 0 + processing_has_refined_job_count = False + job_timestamp = '0' + sampling_step = 0 + sampling_steps = 0 + current_latent = None + current_image = None + current_image_sampling_step = 0 + id_live_preview = 0 + textinfo = None + time_start = None + need_restart = False + server_start = None + + def skip(self): + log.debug('Requested skip') + self.skipped = True + + def interrupt(self): + log.debug('Requested interrupt') + self.interrupted = True + + def pause(self): + self.paused = not self.paused + log.debug(f'Requested {"pause" if self.paused else "continue"}') + + def nextjob(self): + if opts.live_previews_enable and opts.show_progress_every_n_steps == -1: + self.do_set_current_image() + self.job_no += 1 + self.sampling_step = 0 + self.current_image_sampling_step = 0 + + def dict(self): + obj = { + "skipped": self.skipped, + "interrupted": self.interrupted, + "job": self.job, + "job_count": self.job_count, + "job_timestamp": self.job_timestamp, + "job_no": self.job_no, + "sampling_step": self.sampling_step, + "sampling_steps": self.sampling_steps, + } + return obj + + def begin(self): + self.sampling_step = 0 + self.job_count = -1 + self.processing_has_refined_job_count = False + self.job_no = 0 + self.job_timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S") + self.current_latent = None + self.current_image = None + self.current_image_sampling_step = 0 + self.id_live_preview = 0 + self.skipped = False + self.interrupted = False + self.paused = False + self.textinfo = None + self.time_start = time.time() + devices.torch_gc() + + def end(self): + self.job = "" + self.job_count = 0 + self.paused = False + devices.torch_gc() + + def set_current_image(self): + """sets self.current_image from self.current_latent if enough sampling steps have been made after the last call to this""" + if not parallel_processing_allowed: + return + if self.sampling_step - self.current_image_sampling_step >= opts.show_progress_every_n_steps and opts.live_previews_enable and opts.show_progress_every_n_steps != -1: + self.do_set_current_image() + + def do_set_current_image(self): + if self.current_latent is None: + return + import modules.sd_samplers # pylint: disable=W0621 + if opts.show_progress_grid: + self.assign_current_image(modules.sd_samplers.samples_to_image_grid(self.current_latent)) + else: + self.assign_current_image(modules.sd_samplers.sample_to_image(self.current_latent)) + self.current_image_sampling_step = self.sampling_step + + def assign_current_image(self, image): + self.current_image = image + self.id_live_preview += 1 + +state = State() +state.server_start = time.time() + + +class OptionInfo: + def __init__(self, default=None, label="", component=None, component_args=None, onchange=None, section=None, refresh=None, comment_before='', comment_after=''): + self.default = default + self.label = label + self.component = component + self.component_args = component_args + self.onchange = onchange + self.section = section + self.refresh = refresh + self.comment_before = comment_before # HTML text that will be added after label in UI + self.comment_after = comment_after # HTML text that will be added before label in UI + + def link(self, label, uri): + self.comment_before += f"[{label}]" + return self + + def js(self, label, js_func): + self.comment_before += f"[{label}]" + return self + + def info(self, info): + self.comment_after += f"({info})" + return self + + def needs_restart(self): + self.comment_after += " (requires restart)" + return self + + +def options_section(section_identifier, options_dict): + for v in options_dict.values(): + v.section = section_identifier + return options_dict + + +def list_checkpoint_tiles(): + import modules.sd_models # pylint: disable=W0621 + return modules.sd_models.checkpoint_tiles() + + +default_checkpoint = list_checkpoint_tiles()[0] if len(list_checkpoint_tiles()) > 0 else "model.ckpt" + + +def refresh_checkpoints(): + import modules.sd_models # pylint: disable=W0621 + return modules.sd_models.list_models() + + +def list_samplers(): + import modules.sd_samplers # pylint: disable=W0621 + modules.sd_samplers.set_samplers() + return modules.sd_samplers.all_samplers + +def list_themes(): + fn = os.path.join('html', 'themes.json') + if not os.path.exists(fn): + refresh_themes() + if os.path.exists(fn): + with open(fn, mode='r', encoding='utf=8') as f: + res = json.loads(f.read()) + else: + res = [] + builtin = ["black-orange", "gradio/default", "gradio/base", "gradio/glass", "gradio/monochrome", "gradio/soft"] + themes = sorted(set(builtin + [x['id'] for x in res if x['status'] == 'RUNNING' and 'test' not in x['id'].lower()]), key=str.casefold) + return themes + + +def refresh_themes(): + try: + req = requests.get('https://huggingface.co/datasets/freddyaboulton/gradio-theme-subdomains/resolve/main/subdomains.json', timeout=5) + if req.status_code == 200: + res = req.json() + fn = os.path.join('html', 'themes.json') + with open(fn, mode='w', encoding='utf=8') as f: + f.write(json.dumps(res)) + else: + log.error('Error refreshing UI themes') + except: + log.error('Exception refreshing UI themes') + + +if devices.backend == "cpu": + cross_attention_optimization_default = "Doggettx's" +elif devices.backend == "mps": + cross_attention_optimization_default = "Doggettx's" +elif devices.backend == "ipex": + cross_attention_optimization_default = "InvokeAI's" +elif devices.backend == "directml": + cross_attention_optimization_default = "Sub-quadratic" +elif devices.backend == "rocm": + cross_attention_optimization_default = "Sub-quadratic" +else: # cuda + cross_attention_optimization_default ="Scaled-Dot-Product" + +options_templates.update(options_section(('sd', "Stable Diffusion"), { + "sd_model_checkpoint": OptionInfo(default_checkpoint, "Stable Diffusion checkpoint", gr.Dropdown, lambda: {"choices": list_checkpoint_tiles()}, refresh=refresh_checkpoints), + "sd_checkpoint_cache": OptionInfo(0, "Number of cached model checkpoints", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), + "sd_vae_checkpoint_cache": OptionInfo(0, "Number of cached VAE checkpoints", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), + "sd_vae": OptionInfo("Automatic", "Select VAE", gr.Dropdown, lambda: {"choices": shared_items.sd_vae_items()}, refresh=shared_items.refresh_vae_list), + "sd_vae_sliced_encode": OptionInfo(False, "Enable splitting of hires batch processing"), + "stream_load": OptionInfo(False, "When loading models attempt stream loading optimized for slow or network storage"), + "model_reuse_dict": OptionInfo(False, "When loading models attempt to reuse previous model dictionary"), + "cross_attention_optimization": OptionInfo(cross_attention_optimization_default, "Cross-attention optimization method", gr.Radio, lambda: {"choices": shared_items.list_crossattention() }), + "cross_attention_options": OptionInfo([], "Cross-attention advanced options", gr.CheckboxGroup, lambda: {"choices": ['xFormers enable flash Attention', 'SDP disable memory attention']}), + "sub_quad_q_chunk_size": OptionInfo(512, "Sub-quadratic cross-attention query chunk size", gr.Slider, {"minimum": 16, "maximum": 8192, "step": 8}), + "sub_quad_kv_chunk_size": OptionInfo(512, "Sub-quadratic cross-attention kv chunk size", gr.Slider, {"minimum": 0, "maximum": 8192, "step": 8}), + "sub_quad_chunk_threshold": OptionInfo(80, "Sub-quadratic cross-attention chunking threshold", gr.Slider, {"minimum": 0, "maximum": 100, "step": 1}), + "prompt_attention": OptionInfo("Full parser", "Prompt attention parser", gr.Radio, lambda: {"choices": ["Full parser", "Compel parser", "A1111 parser", "Fixed attention"] }), + "prompt_mean_norm": OptionInfo(True, "Prompt attention mean normalization"), + "always_batch_cond_uncond": OptionInfo(False, "Disable conditional batching enabled on low memory systems"), + "enable_quantization": OptionInfo(True, "Enable samplers quantization for sharper and cleaner results"), + "comma_padding_backtrack": OptionInfo(20, "Increase coherency by padding from the last comma within n tokens when using more than 75 tokens", gr.Slider, {"minimum": 0, "maximum": 74, "step": 1 }), + "sd_backend": OptionInfo("Original", "Stable Diffusion backend (experimental)", gr.Radio, lambda: {"choices": ["Original", "Diffusers"] }), +})) + +options_templates.update(options_section(('cuda', "Compute Settings"), { + "memmon_poll_rate": OptionInfo(2, "VRAM usage polls per second during generation", gr.Slider, {"minimum": 0, "maximum": 40, "step": 1}), + "precision": OptionInfo("Autocast", "Precision type", gr.Radio, lambda: {"choices": ["Autocast", "Full"]}), + "cuda_dtype": OptionInfo("FP32" if sys.platform == "darwin" else "FP16", "Device precision type", gr.Radio, lambda: {"choices": ["FP32", "FP16", "BF16"]}), + "no_half": OptionInfo(False, "Use full precision for model (--no-half)", None, None, None), + "no_half_vae": OptionInfo(False, "Use full precision for VAE (--no-half-vae)"), + "upcast_sampling": OptionInfo(True if sys.platform == "darwin" else False, "Enable upcast sampling"), + "upcast_attn": OptionInfo(False, "Enable upcast cross attention layer"), + "disable_nan_check": OptionInfo(True, "Disable NaN check in produced images/latent spaces"), + "rollback_vae": OptionInfo(False, "Attempt to roll back VAE when produced NaN values, requires NaN check (experimental)"), + "opt_channelslast": OptionInfo(False, "Use channels last as torch memory format "), + "cudnn_benchmark": OptionInfo(False, "Enable full-depth cuDNN benchmark feature"), + "cuda_allow_tf32": OptionInfo(True, "Allow TF32 math ops"), + "cuda_allow_tf16_reduced": OptionInfo(True, "Allow TF16 reduced precision math ops"), + "cuda_compile": OptionInfo(False, "Enable model compile (experimental)"), + "cuda_compile_mode": OptionInfo("none", "Model compile mode (experimental)", gr.Radio, lambda: {"choices": ['none', 'inductor', 'cudagraphs', 'aot_ts_nvfuser', 'hidet', 'ipex']}), + "cuda_compile_verbose": OptionInfo(False, "Model compile verbose mode"), + "cuda_compile_errors": OptionInfo(True, "Model compile suppress errors"), + "disable_gc": OptionInfo(False, "Disable Torch memory garbage collection (experimental)"), +})) + +options_templates.update(options_section(('system-paths', "System Paths"), { + "temp_dir": OptionInfo("", "Directory for temporary images; leave empty for default"), + "clean_temp_dir_at_start": OptionInfo(True, "Cleanup non-default temporary directory when starting webui"), + "ckpt_dir": OptionInfo(os.path.join(paths.models_path, 'Stable-diffusion'), "Path to directory with stable diffusion checkpoints"), + "diffusers_dir": OptionInfo(os.path.join(paths.models_path, 'Diffusers'), "Path to directory with stable diffusion diffusers"), + "vae_dir": OptionInfo(os.path.join(paths.models_path, 'VAE'), "Path to directory with VAE files"), + "embeddings_dir": OptionInfo(os.path.join(paths.models_path, 'embeddings'), "Embeddings directory for textual inversion"), + "hypernetwork_dir": OptionInfo(os.path.join(paths.models_path, 'hypernetworks'), "Hypernetwork directory"), + "codeformer_models_path": OptionInfo(os.path.join(paths.models_path, 'Codeformer'), "Path to directory with codeformer model file(s)"), + "gfpgan_models_path": OptionInfo(os.path.join(paths.models_path, 'GFPGAN'), "Path to directory with GFPGAN model file(s)"), + "esrgan_models_path": OptionInfo(os.path.join(paths.models_path, 'ESRGAN'), "Path to directory with ESRGAN model file(s)"), + "bsrgan_models_path": OptionInfo(os.path.join(paths.models_path, 'BSRGAN'), "Path to directory with BSRGAN model file(s)"), + "realesrgan_models_path": OptionInfo(os.path.join(paths.models_path, 'RealESRGAN'), "Path to directory with RealESRGAN model file(s)"), + "scunet_models_path": OptionInfo(os.path.join(paths.models_path, 'ScuNET'), "Path to directory with ScuNET model file(s)"), + "swinir_models_path": OptionInfo(os.path.join(paths.models_path, 'SwinIR'), "Path to directory with SwinIR model file(s)"), + "ldsr_models_path": OptionInfo(os.path.join(paths.models_path, 'LDSR'), "Path to directory with LDSR model file(s)"), + "clip_models_path": OptionInfo(os.path.join(paths.models_path, 'CLIP'), "Path to directory with CLIP model file(s)"), + "lora_dir": OptionInfo(os.path.join(paths.models_path, 'Lora'), "Path to directory with Lora network(s)"), + "lyco_dir": OptionInfo(os.path.join(paths.models_path, 'LyCORIS'), "Path to directory with LyCORIS network(s)"), + "styles_dir": OptionInfo(os.path.join(paths.data_path, 'styles.csv'), "Path to user-defined styles file"), +})) + +options_templates.update(options_section(('saving-images', "Image Options"), { + "samples_save": OptionInfo(True, "Always save all generated images"), + "samples_format": OptionInfo('jpg', 'File format for generated images', gr.Dropdown, lambda: {"choices": ["jpg", "png", "webp", "tiff", "jp2"]}), + "samples_filename_pattern": OptionInfo("[seed]-[prompt_spaces]", "Images filename pattern", component_args=hide_dirs), + "save_images_add_number": OptionInfo(True, "Add number to filename when saving", component_args=hide_dirs), + "grid_save": OptionInfo(True, "Always save all generated image grids"), + "grid_format": OptionInfo('jpg', 'File format for grids', gr.Dropdown, lambda: {"choices": ["jpg", "png", "webp", "tiff", "jp2"]}), + "grid_extended_filename": OptionInfo(True, "Add extended info (seed, prompt) to filename when saving grid"), + "grid_only_if_multiple": OptionInfo(True, "Do not save grids consisting of one picture"), + "grid_prevent_empty_spots": OptionInfo(True, "Prevent empty spots in grid (when set to autodetect)"), + "n_rows": OptionInfo(-1, "Grid row count; use -1 for autodetect and 0 for it to be same as batch size", gr.Slider, {"minimum": -1, "maximum": 16, "step": 1}), + "save_txt": OptionInfo(False, "Create a text file next to every image with generation parameters"), + "save_log_fn": OptionInfo("", "Create a JSON log file with image information for each saved image", component_args=hide_dirs), + "save_images_before_face_restoration": OptionInfo(False, "Save a copy of image before doing face restoration"), + "save_images_before_highres_fix": OptionInfo(False, "Save a copy of image before applying highres fix"), + "save_images_before_color_correction": OptionInfo(False, "Save a copy of image before applying color correction to img2img results"), + "save_mask": OptionInfo(False, "Save a copy of the inpainting greyscale mask"), + "save_mask_composite": OptionInfo(False, "Save a copy of inpainting masked composite"), + "save_init_img": OptionInfo(False, "Save a copy of processing init images"), + "jpeg_quality": OptionInfo(85, "Quality for saved jpeg images", gr.Slider, {"minimum": 1, "maximum": 100, "step": 1}), + "webp_lossless": OptionInfo(False, "Use lossless compression for webp images"), + "img_max_size_mp": OptionInfo(250, "Maximum allowed image size in megapixels", gr.Number), + "use_original_name_batch": OptionInfo(True, "Use original name for output filename during batch process in extras tab"), + "use_upscaler_name_as_suffix": OptionInfo(True, "Use upscaler name as filename suffix in the extras tab"), + "save_selected_only": OptionInfo(True, "When using 'Save' button, only save a single selected image"), + "save_to_dirs": OptionInfo(False, "Save images to a subdirectory"), + "grid_save_to_dirs": OptionInfo(False, "Save grids to a subdirectory"), + "use_save_to_dirs_for_ui": OptionInfo(False, "Save images to a subdirectory when using Save button"), + "directories_filename_pattern": OptionInfo("[date]", "Directory name pattern", component_args=hide_dirs), + "directories_max_prompt_words": OptionInfo(8, "Max prompt words for [prompt_words] pattern", gr.Slider, {"minimum": 1, "maximum": 20, "step": 1, **hide_dirs}), +})) + +options_templates.update(options_section(('image-processing', "Image Processing"), { + "img2img_color_correction": OptionInfo(False, "Apply color correction to match original colors"), + "img2img_fix_steps": OptionInfo(False, "For image processing do exact number of steps as specified"), + "img2img_background_color": OptionInfo("#ffffff", "Image transparent color fill", ui_components.FormColorPicker, {}), + "inpainting_mask_weight": OptionInfo(1.0, "Inpainting conditioning mask strength", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), + "initial_noise_multiplier": OptionInfo(1.0, "Noise multiplier for image processing", gr.Slider, {"minimum": 0.1, "maximum": 1.5, "step": 0.01}), + "CLIP_stop_at_last_layers": OptionInfo(1, "Clip skip", gr.Slider, {"minimum": 1, "maximum": 8, "step": 1, "visible": False}), +})) + + +options_templates.update(options_section(('saving-paths', "Output Paths"), { + "outdir_samples": OptionInfo("", "Output directory for images; if empty, defaults to three directories below", component_args=hide_dirs), + "outdir_txt2img_samples": OptionInfo("outputs/text", 'Output directory for txt2img images', component_args=hide_dirs), + "outdir_img2img_samples": OptionInfo("outputs/image", 'Output directory for img2img images', component_args=hide_dirs), + "outdir_extras_samples": OptionInfo("outputs/extras", 'Output directory for images from extras tab', component_args=hide_dirs), + "outdir_grids": OptionInfo("", "Output directory for grids; if empty, defaults to two directories below", component_args=hide_dirs), + "outdir_txt2img_grids": OptionInfo("outputs/grids", 'Output directory for txt2img grids', component_args=hide_dirs), + "outdir_img2img_grids": OptionInfo("outputs/grids", 'Output directory for img2img grids', component_args=hide_dirs), + "outdir_save": OptionInfo("outputs/save", "Directory for saving images using the Save button", component_args=hide_dirs), + "outdir_init_images": OptionInfo("outputs/init-images", "Directory for saving init images when using img2img", component_args=hide_dirs), +})) + +options_templates.update(options_section(('ui', "User interface"), { + "gradio_theme": OptionInfo("black-orange", "UI theme", gr.Dropdown, lambda: {"choices": list_themes()}, refresh=refresh_themes), + "theme_style": OptionInfo("Auto", "Theme mode", gr.Radio, {"choices": ["Auto", "Dark", "Light"]}), + "return_grid": OptionInfo(True, "Show grid in results for web"), + "return_mask": OptionInfo(False, "For inpainting, include the greyscale mask in results for web"), + "return_mask_composite": OptionInfo(False, "For inpainting, include masked composite in results for web"), + "disable_weights_auto_swap": OptionInfo(True, "Do not change the selected model when reading generation parameters"), + "send_seed": OptionInfo(True, "Send seed when sending prompt or image to other interface"), + "send_size": OptionInfo(True, "Send size when sending prompt or image to another interface"), + "font": OptionInfo("", "Font for image grids that have text"), + "keyedit_precision_attention": OptionInfo(0.1, "Ctrl+up/down precision when editing (attention:1.1)", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}), + "keyedit_precision_extra": OptionInfo(0.05, "Ctrl+up/down precision when editing ", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}), + "keyedit_delimiters": OptionInfo(".,\/!?%^*;:{}=`~()", "Ctrl+up/down word delimiters"), # pylint: disable=anomalous-backslash-in-string + "quicksettings_list": OptionInfo(["sd_model_checkpoint"], "Quicksettings list", ui_components.DropdownMulti, lambda: {"choices": list(opts.data_labels.keys())}), + "hidden_tabs": OptionInfo([], "Hidden UI tabs", ui_components.DropdownMulti, lambda: {"choices": [x for x in tab_names]}), + "ui_tab_reorder": OptionInfo("From Text, From Image, Process Image", "UI tabs order"), + "ui_scripts_reorder": OptionInfo("Enable Dynamic Thresholding, ControlNet", "UI scripts order"), + "ui_reorder": OptionInfo(", ".join(ui_reorder_categories), "txt2img/img2img UI item order"), + "ui_extra_networks_tab_reorder": OptionInfo("Checkpoints, Lora, LyCORIS, Textual Inversion, Hypernetworks", "Extra networks tab order"), +})) + +options_templates.update(options_section(('live-preview', "Live previews"), { + "show_progressbar": OptionInfo(True, "Show progressbar"), + "live_previews_enable": OptionInfo(True, "Show live previews of the created image"), + "show_progress_grid": OptionInfo(True, "Show previews of all images generated in a batch as a grid"), + "notification_audio_enable": OptionInfo(False, "Play a sound when images are finished generating"), + "notification_audio_path": OptionInfo("html/notification.mp3","Path to notification sound", component_args=hide_dirs), + "show_progress_every_n_steps": OptionInfo(1, "Live preview display period", gr.Slider, {"minimum": -1, "maximum": 32, "step": 1}), + "show_progress_type": OptionInfo("TAESD", "Live preview method", gr.Radio, {"choices": ["Full", "Approx NN", "Approx cheap", "TAESD"]}), + "live_preview_content": OptionInfo("Combined", "Live preview subject", gr.Radio, {"choices": ["Combined", "Prompt", "Negative prompt"]}), + "live_preview_refresh_period": OptionInfo(250, "Progressbar/preview update period, in milliseconds") +})) + +options_templates.update(options_section(('sampler-params', "Sampler Settings"), { + "show_samplers": OptionInfo(["Euler a", "UniPC", "DDIM", "DPM++ 2M SDE", "DPM++ 2M SDE Karras", "DPM2 Karras", "DPM++ 2M Karras"], "Show samplers in user interface", gr.CheckboxGroup, lambda: {"choices": [x.name for x in list_samplers() if x.name != "PLMS"]}), + "fallback_sampler": OptionInfo("Euler a", "Secondary sampler", gr.Dropdown, lambda: {"choices": ["None"] + [x.name for x in list_samplers()]}), + "xyz_fallback_sampler": OptionInfo("None", "Force latent upscaler sampler", gr.Dropdown, lambda: {"choices": ["None"] + [x.name for x in list_samplers()]}), + "eta_ancestral": OptionInfo(1.0, "Noise multiplier for ancestral samplers (eta)", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), + "eta_ddim": OptionInfo(0.0, "Noise multiplier for DDIM (eta)", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), + "ddim_discretize": OptionInfo('uniform', "DDIM discretize img2img", gr.Radio, {"choices": ['uniform', 'quad']}), + 's_churn': OptionInfo(0.0, "sigma churn", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), + 's_min_uncond': OptionInfo(0, "Negative Guidance minimum sigma", gr.Slider, {"minimum": 0.0, "maximum": 4.0, "step": 0.01}), + 's_tmin': OptionInfo(0.0, "sigma tmin", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), + 's_noise': OptionInfo(1.0, "sigma noise", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), + 'eta_noise_seed_delta': OptionInfo(0, "Noise seed delta (eta)", gr.Number, {"precision": 0}), + 'always_discard_next_to_last_sigma': OptionInfo(False, "Always discard next-to-last sigma"), + 'uni_pc_variant': OptionInfo("bh1", "UniPC variant", gr.Radio, {"choices": ["bh1", "bh2", "vary_coeff"]}), + 'uni_pc_skip_type': OptionInfo("time_uniform", "UniPC skip type", gr.Radio, {"choices": ["time_uniform", "time_quadratic", "logSNR"]}), + 'uni_pc_order': OptionInfo(3, "UniPC order (must be < sampling steps)", gr.Slider, {"minimum": 1, "maximum": 50, "step": 1}), + 'uni_pc_lower_order_final': OptionInfo(True, "UniPC lower order final"), +})) + +options_templates.update(options_section(('postprocessing', "Postprocessing"), { + 'postprocessing_enable_in_main_ui': OptionInfo([], "Enable addtional postprocessing operations", ui_components.DropdownMulti, lambda: {"choices": [x.name for x in shared_items.postprocessing_scripts()]}), + 'postprocessing_operation_order': OptionInfo([], "Postprocessing operation order", ui_components.DropdownMulti, lambda: {"choices": [x.name for x in shared_items.postprocessing_scripts()]}), + 'upscaling_max_images_in_cache': OptionInfo(5, "Maximum number of images in upscaling cache", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), +})) + +options_templates.update(options_section(('training', "Training"), { + "unload_models_when_training": OptionInfo(False, "Move VAE and CLIP to RAM when training if possible"), + "pin_memory": OptionInfo(True, "Pin training dataset to memory"), + "save_optimizer_state": OptionInfo(False, "Saves resumable optimizer state when training embedding or hypernetwork"), + "save_training_settings_to_txt": OptionInfo(True, "Save textual inversion and hypernet settings to a text file whenever training starts"), + "dataset_filename_word_regex": OptionInfo("", "Filename word regex"), + "dataset_filename_join_string": OptionInfo(" ", "Filename join string"), + "embeddings_templates_dir": OptionInfo(os.path.join(paths.script_path, 'train', 'templates'), "Embeddings train templates directory"), + "training_image_repeats_per_epoch": OptionInfo(1, "Number of repeats for a single input image per epoch; used only for displaying epoch number", gr.Number, {"precision": 0}), + "training_write_csv_every": OptionInfo(0, "Save an csv containing the loss to log directory every N steps, 0 to disable"), + "training_enable_tensorboard": OptionInfo(False, "Enable tensorboard logging"), + "training_tensorboard_save_images": OptionInfo(False, "Save generated images within tensorboard"), + "training_tensorboard_flush_every": OptionInfo(120, "How often, in seconds, to flush the pending tensorboard events and summaries to disk"), +})) + +options_templates.update(options_section(('interrogate', "Interrogate"), { + "interrogate_keep_models_in_memory": OptionInfo(False, "Interrogate: keep models in VRAM"), + "interrogate_return_ranks": OptionInfo(True, "Interrogate: include ranks of model tags matches in results"), + "interrogate_clip_num_beams": OptionInfo(1, "Interrogate: num_beams for BLIP", gr.Slider, {"minimum": 1, "maximum": 16, "step": 1}), + "interrogate_clip_min_length": OptionInfo(32, "Interrogate: minimum description length (excluding artists, etc..)", gr.Slider, {"minimum": 1, "maximum": 128, "step": 1}), + "interrogate_clip_max_length": OptionInfo(192, "Interrogate: maximum description length", gr.Slider, {"minimum": 1, "maximum": 256, "step": 1}), + "interrogate_clip_dict_limit": OptionInfo(2048, "CLIP: maximum number of lines in text file (0 = No limit)"), + "interrogate_clip_skip_categories": OptionInfo(["artists", "movements", "flavors"], "CLIP: skip inquire categories", gr.CheckboxGroup, lambda: {"choices": modules.interrogate.category_types()}, refresh=modules.interrogate.category_types), + "interrogate_deepbooru_score_threshold": OptionInfo(0.65, "Interrogate: deepbooru score threshold", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01}), + "deepbooru_sort_alpha": OptionInfo(False, "Interrogate: deepbooru sort alphabetically"), + "deepbooru_use_spaces": OptionInfo(False, "use spaces for tags in deepbooru"), + "deepbooru_escape": OptionInfo(True, "escape (\\) brackets in deepbooru (so they are used as literal brackets and not for emphasis)"), + "deepbooru_filter_tags": OptionInfo("", "filter out those tags from deepbooru output (separated by comma)"), +})) + +options_templates.update(options_section(('upscaling', "Upscaling"), { + "upscaler_for_img2img": OptionInfo("None", "Default upscaler for image resize operations", gr.Dropdown, lambda: {"choices": [x.name for x in sd_upscalers]}), + "realesrgan_enabled_models": OptionInfo(["R-ESRGAN 4x+", "R-ESRGAN 4x+ Anime6B"], "Real-ESRGAN available models", gr.CheckboxGroup, lambda: {"choices": shared_items.realesrgan_models_names()}), + "ESRGAN_tile": OptionInfo(192, "Tile size for ESRGAN upscalers (0 = no tiling)", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}), + "ESRGAN_tile_overlap": OptionInfo(8, "Tile overlap in pixels for ESRGAN upscalers", gr.Slider, {"minimum": 0, "maximum": 48, "step": 1}), + "SCUNET_tile": OptionInfo(256, "Tile size for SCUNET upscalers. 0 = no tiling.", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}), + "SCUNET_tile_overlap": OptionInfo(8, "Tile overlap, in pixels for SCUNET upscalers. Low values = visible seam.", gr.Slider, {"minimum": 0, "maximum": 64, "step": 1}), + "use_old_hires_fix_width_height": OptionInfo(False, "Hires fix uses width & height to set final resolution rather than first pass"), + "dont_fix_second_order_samplers_schedule": OptionInfo(False, "Do not fix prompt schedule for second order samplers"), + "lyco_patch_lora": OptionInfo(False, "Use LyCoris handler for all Lora types", gr.Checkbox, { "visible": False }), # TODO: lyco-patch-lora + "lora_functional": OptionInfo(False, "Use Kohya method for handling multiple Loras", gr.Checkbox, { "visible": False }), +})) + +# options_templates.update(options_section(('lora', "Lora"), { +# })) + +options_templates.update(options_section(('face-restoration', "Face restoration"), { + "face_restoration_model": OptionInfo("CodeFormer", "Face restoration model", gr.Radio, lambda: {"choices": [x.name() for x in face_restorers]}), + "code_former_weight": OptionInfo(0.2, "CodeFormer weight parameter; 0 = maximum effect; 1 = minimum effect", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01}), + "face_restoration_unload": OptionInfo(False, "Move face restoration model from VRAM into RAM after processing"), +})) + +options_templates.update(options_section(('extra_networks', "Extra Networks"), { + "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"), + "sd_hypernetwork": OptionInfo("None", "Add hypernetwork to prompt", gr.Dropdown, lambda: {"choices": ["None"] + [x for x in hypernetworks.keys()]}, refresh=reload_hypernetworks), +})) + +options_templates.update(options_section(('token_merging', 'Token Merging'), { + "token_merging": OptionInfo(False, "Enable redundant token merging via tomesd for speed and memory improvements", gr.Checkbox), + "token_merging_ratio": OptionInfo(0.5, "Token merging Ratio. Higher merging ratio = faster generation, smaller VRAM usage, lower quality.", gr.Slider, {"minimum": 0, "maximum": 0.9, "step": 0.1}), + "token_merging_hr_only": OptionInfo(True, "Apply only to high-res fix pass. Disabling can yield a ~20-35% speedup on contemporary resolutions.", gr.Checkbox), + "token_merging_ratio_hr": OptionInfo(0.5, "Merging Ratio (high-res pass) - If 'Apply only to high-res' is enabled, this will always be the ratio used.", gr.Slider, {"minimum": 0, "maximum": 0.9, "step": 0.1}), + "token_merging_random": OptionInfo(False, "Use random perturbations - Can improve outputs for certain samplers. For others, it may cause visual artifacting.", gr.Checkbox), + "token_merging_merge_attention": OptionInfo(True, "Merge attention (Recommend on)", gr.Checkbox), + "token_merging_merge_cross_attention": OptionInfo(False, "Merge cross attention (Recommend off)", gr.Checkbox), + "token_merging_merge_mlp": OptionInfo(False, "Merge mlp (Strongly recommend off)", gr.Checkbox), + "token_merging_maximum_down_sampling": OptionInfo(1, "Maximum down sampling", gr.Radio, lambda: {"choices": [1, 2, 4, 8]}), + "token_merging_stride_x": OptionInfo(2, "Stride - X", gr.Slider, {"minimum": 2, "maximum": 8, "step": 2}), + "token_merging_stride_y": OptionInfo(2, "Stride - Y", gr.Slider, {"minimum": 2, "maximum": 8, "step": 2}) +})) + +options_templates.update(options_section((None, "Hidden options"), { + "disabled_extensions": OptionInfo([], "Disable these extensions"), + "disable_all_extensions": OptionInfo("none", "Disable all extensions (preserves the list of disabled extensions)", gr.Radio, {"choices": ["none", "user", "all"]}), + "sd_checkpoint_hash": OptionInfo("", "SHA256 hash of the current checkpoint"), +})) + +options_templates.update() + + +class Options: + data = None + data_labels = options_templates + typemap = {int: float} + + def __init__(self): + self.data = {k: v.default for k, v in self.data_labels.items()} + + def __setattr__(self, key, value): + if self.data is not None: + if key in self.data or key in self.data_labels: + if cmd_opts.freeze: + log.warning(f'Settings are frozen: {key}') + return + if cmd_opts.hide_ui_dir_config and key in restricted_opts: + log.warning(f'Settings key is restricted: {key}') + return + else: + self.data[key] = value + return + + return super(Options, self).__setattr__(key, value) + + def __getattr__(self, item): + if self.data is not None: + if item in self.data: + return self.data[item] + if item in self.data_labels: + return self.data_labels[item].default + return super(Options, self).__getattribute__(item) + + def set(self, key, value): + """sets an option and calls its onchange callback, returning True if the option changed and False otherwise""" + oldval = self.data.get(key, None) + if oldval == value: + return False + try: + setattr(self, key, value) + except RuntimeError: + return False + if self.data_labels[key].onchange is not None: + try: + self.data_labels[key].onchange() + except Exception as e: + errors.display(e, f"changing setting {key} to {value}") + setattr(self, key, oldval) + return False + return True + + def get_default(self, key): + """returns the default value for the key""" + data_label = self.data_labels.get(key) + if data_label is None: + return None + return data_label.default + + def save(self, filename): + if cmd_opts.freeze: + log.warning(f'Settings saving is disabled: {filename}') + return + with open(filename, "w", encoding="utf8") as file: + json.dump(self.data, file, indent=4) + + def same_type(self, x, y): + if x is None or y is None: + return True + type_x = self.typemap.get(type(x), type(x)) + type_y = self.typemap.get(type(y), type(y)) + return type_x == type_y + + def load(self, filename): + if not os.path.isfile(filename): + log.debug(f'Created default config: {filename}') + self.save(filename) + return + with open(filename, "r", encoding="utf8") as file: + self.data = json.load(file) + if self.data.get('quicksettings') is not None and self.data.get('quicksettings_list') is None: + self.data['quicksettings_list'] = [i.strip() for i in self.data.get('quicksettings').split(',')] + bad_settings = 0 + for k, v in self.data.items(): + info = self.data_labels.get(k, None) + if info is not None and not self.same_type(info.default, v): + log.error(f"Warning: bad setting value: {k}: {v} ({type(v).__name__}; expected {type(info.default).__name__})") + bad_settings += 1 + if bad_settings > 0: + log.error(f"Error: Bad settings found in {filename}") + + def onchange(self, key, func, call=True): + item = self.data_labels.get(key) + item.onchange = func + if call: + func() + + def dumpjson(self): + d = {k: self.data.get(k, self.data_labels.get(k).default) for k in self.data_labels.keys()} + metadata = { + k: { + "is_stored": k in self.data, + "tab_name": v.section[0] + } for k, v in self.data_labels.items() + } + return json.dumps({"values": d, "metadata": metadata}) + + def add_option(self, key, info): + self.data_labels[key] = info + + def reorder(self): + """reorder settings so that all items related to section always go together""" + section_ids = {} + settings_items = self.data_labels.items() + for k, item in settings_items: + if item.section not in section_ids: + section_ids[item.section] = len(section_ids) + self.data_labels = {k: v for k, v in sorted(settings_items, key=lambda x: section_ids[x[1].section])} + + def cast_value(self, key, value): + """casts an arbitrary to the same type as this setting's value with key + Example: cast_value("eta_noise_seed_delta", "12") -> returns 12 (an int rather than str) + """ + if value is None: + return None + default_value = self.data_labels[key].default + if default_value is None: + default_value = getattr(self, key, None) + if default_value is None: + return None + expected_type = type(default_value) + if expected_type == bool and value == "False": + value = False + elif expected_type == type(value): + pass + else: + value = expected_type(value) + return value + + +opts = Options() +config_filename = cmd_opts.config +opts.load(config_filename) +cmd_opts = cmd_args.compatibility_args(opts, cmd_opts) +if cmd_opts.backend == 'diffusers': + log.info('Overriding backend to Diffusers') + opts.data['sd_backend'] = 'Diffusers' +if cmd_opts.backend == 'original': + log.info('Overriding backend to Diffusers') + opts.data['sd_backend'] = 'Original' +backend = Backend.DIFFUSERS if opts.sd_backend == 'Diffusers' else Backend.ORIGINAL + +prompt_styles = modules.styles.StyleDatabase(opts.styles_dir) +cmd_opts.disable_extension_access = (cmd_opts.share or cmd_opts.listen or (cmd_opts.server_name or False)) and not cmd_opts.insecure +devices.device, devices.device_interrogate, devices.device_gfpgan, devices.device_esrgan, devices.device_codeformer = (devices.cpu if any(y in cmd_opts.use_cpu for y in [x, 'all']) else devices.get_optimal_device() for x in ['sd', 'interrogate', 'gfpgan', 'esrgan', 'codeformer']) +device = devices.device +batch_cond_uncond = opts.always_batch_cond_uncond or not (cmd_opts.lowvram or cmd_opts.medvram) +parallel_processing_allowed = not cmd_opts.lowvram and not cmd_opts.medvram +mem_mon = modules.memmon.MemUsageMonitor("MemMon", device, opts) +mem_mon.start() +if device.type == 'privateuseone': + import modules.dml # pylint: disable=ungrouped-imports + + +def reload_gradio_theme(theme_name=None): + global gradio_theme # pylint: disable=global-statement + if not theme_name: + theme_name = opts.gradio_theme + default_font_params = {} + res = 0 + try: + req = urllib.request.Request("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono", method="HEAD") + res = urllib.request.urlopen(req, timeout=3.0).status + except: + res = 0 + if res != 200: + log.info('No internet access detected, using default fonts') + default_font_params = { + 'font':['Helvetica', 'ui-sans-serif', 'system-ui', 'sans-serif'], + 'font_mono':['IBM Plex Mono', 'ui-monospace', 'Consolas', 'monospace'] + } + if theme_name == "black-orange": + gradio_theme = gr.themes.Default(**default_font_params) + elif theme_name.startswith("gradio/"): + if theme_name == "gradio/default": + gradio_theme = gr.themes.Default(**default_font_params) + if theme_name == "gradio/base": + gradio_theme = gr.themes.Base(**default_font_params) + if theme_name == "gradio/glass": + gradio_theme = gr.themes.Glass(**default_font_params) + if theme_name == "gradio/monochrome": + gradio_theme = gr.themes.Monochrome(**default_font_params) + if theme_name == "gradio/soft": + gradio_theme = gr.themes.Soft(**default_font_params) + else: + try: + gradio_theme = gr.themes.ThemeClass.from_hub(theme_name) + except: + log.error("Theme download error accessing HuggingFace") + gradio_theme = gr.themes.Default(**default_font_params) + log.info(f'Loading UI theme: name={theme_name} style={opts.theme_style}') + + +class TotalTQDM: + def __init__(self): + self._tqdm = None + + def reset(self): + self._tqdm = tqdm.tqdm( + desc="Total", + total=state.job_count * state.sampling_steps, + position=1, + ) + + def update(self): + if not opts.multiple_tqdm or cmd_opts.disable_console_progressbars: + return + if self._tqdm is None: + self.reset() + self._tqdm.update() + + def updateTotal(self, new_total): + if not opts.multiple_tqdm or cmd_opts.disable_console_progressbars: + return + if self._tqdm is None: + self.reset() + self._tqdm.total = new_total + + def clear(self): + if self._tqdm is not None: + self._tqdm.refresh() + self._tqdm.close() + self._tqdm = None + +total_tqdm = TotalTQDM() + + +def restart_server(restart=True): + if demo is None: + return + log.info('Server shutdown requested') + try: + demo.server.wants_restart = restart + demo.server.should_exit = True + demo.server.force_exit = True + demo.close(verbose=False) + demo.server.close() + demo.fns = [] + except: + pass + if restart: + log.info('Server will restart') + + +def restore_defaults(restart=True): + if os.path.exists(cmd_opts.config): + log.info('Restoring server defaults') + os.remove(cmd_opts.config) + if os.path.exists(cmd_opts.ui_config): + log.info('Restoring UI defaults') + os.remove(cmd_opts.ui_config) + restart_server(restart) + + +def listfiles(dirname): + filenames = [os.path.join(dirname, x) for x in sorted(os.listdir(dirname), key=str.lower) if not x.startswith(".")] + return [file for file in filenames if os.path.isfile(file)] + + +def walk_files(path, allowed_extensions=None): + if not os.path.exists(path): + return + if allowed_extensions is not None: + allowed_extensions = set(allowed_extensions) + for root, _dirs, files in os.walk(path, followlinks=True): + for filename in files: + if allowed_extensions is not None: + _, ext = os.path.splitext(filename) + if ext not in allowed_extensions: + continue + yield os.path.join(root, filename) + + +def html_path(filename): + return os.path.join(paths.script_path, "html", filename) + + +def html(filename): + path = html_path(filename) + if os.path.exists(path): + with open(path, encoding="utf8") as file: + return file.read() + return "" + + +def get_version(): + version = None + if version is None: + try: + import subprocess + res = subprocess.run('git log --pretty=format:"%h %ad" -1 --date=short', stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell=True, check=True) + ver = res.stdout.decode(encoding = 'utf8', errors='ignore') if len(res.stdout) > 0 else ' ' + githash, updated = ver.split(' ') + res = subprocess.run('git remote get-url origin', stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell=True, check=True) + origin = res.stdout.decode(encoding = 'utf8', errors='ignore') if len(res.stdout) > 0 else '' + res = subprocess.run('git branch --show-current', stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell=True, check=True) + branch = res.stdout.decode(encoding = 'utf8', errors='ignore') if len(res.stdout) > 0 else '' + version = { + 'app': 'sd.next', + 'updated': updated, + 'hash': githash, + 'url': origin.replace('\n', '') + '/tree/' + branch.replace('\n', '') + } + except: + version = { 'app': 'sd.next' } + return version + + +class Shared(sys.modules[__name__].__class__): + # this class is here to provide sd_model field as a property, so that it can be created and loaded on demand rather than at program startup. + sd_model_val = None + + @property + def sd_model(self): + import modules.sd_models # pylint: disable=W0621 + # return modules.sd_models.model_data.sd_model + return modules.sd_models.model_data.get_sd_model() + + @sd_model.setter + def sd_model(self, value): + import modules.sd_models # pylint: disable=W0621 + modules.sd_models.model_data.set_sd_model(value) + +# sd_model: LatentDiffusion = None # this var is here just for IDE's type checking; it cannot be accessed because the class field above will be accessed instead +sd_model = None +sys.modules[__name__].__class__ = Shared From 69ab29b0970fb27ad0d4d1be9eb7145d193e40d9 Mon Sep 17 00:00:00 2001 From: Alex Heller Date: Thu, 8 Jun 2023 18:46:41 +0200 Subject: [PATCH 27/31] Moved dirty setting tab indicator element creation to python backend side --- javascript/style.css | 1 - javascript/ui.js | 17 +++-------------- modules/ui.py | 23 ++++++++++++++++++++++- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/javascript/style.css b/javascript/style.css index 3838c1b1b..c14e19200 100644 --- a/javascript/style.css +++ b/javascript/style.css @@ -272,7 +272,6 @@ div#extras_scale_to_tab div.form{ #settings .modification-indicator { width: 100%; height: 100%; - background: var(--color-accent); border-radius: 1em !important; padding: 0; } diff --git a/javascript/ui.js b/javascript/ui.js index 0507df6d5..fb9422c67 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -322,14 +322,10 @@ onUiLoaded(function(){ tab_nav_element.parentElement.insertBefore(tab_content_wrapper, tab_nav_element.nextSibling) tab_elements.forEach(function(elem, index){ - // Add a modification indicator to the toplevel tab button + // Move the modification indicator to the toplevel tab button let tab_name = elem.id.replace("settings_", "") - let new_indicator = document.createElement('button') - new_indicator.id = "modification_indicator_"+tab_name - new_indicator.className = "modification-indicator" - new_indicator.disabled = true - new_indicator.onclick = () => onSettingsModificationIndicatorClicked(tab_name) - tab_nav_element.insertBefore(new_indicator, tab_nav_buttons[index]) + let indicator = gradioApp().getElementById("modification_indicator_"+tab_name) + tab_nav_element.insertBefore(indicator, tab_nav_buttons[index]) // Add the tab content to the wrapper tab_content_wrapper.appendChild(elem) @@ -374,13 +370,6 @@ function markIfModified(setting_name, value) { tab_nav_indicator.title += `${unsaved.size} new default value${unsaved.size > 1 ? 's':''} (not yet saved).`; } -function onSettingsModificationIndicatorClicked(tab_name) { - opts_tabs[tab_name].changed.forEach(function(setting_name){ - // Click each setting's modification indicator - gradioApp().getElementById("modification_indicator_"+setting_name).click() - }) -} - let txt2img_textarea; let img2img_textarea; const wait_time = 800; diff --git a/modules/ui.py b/modules/ui.py index 3684a5875..aeaa64d77 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1306,6 +1306,20 @@ def create_ui(): return res + def create_dirty_indicator(key, keys_to_reset, **kwargs): + def get_opt_values(): + return [getattr(opts, _key) for _key in keys_to_reset] + + elements_to_reset = [component_dict[_key] for _key in keys_to_reset] + indicator = gr.Button( + "", + elem_classes="modification-indicator", + elem_id="modification_indicator_" + key, + **kwargs + ) + indicator.click(fn=get_opt_values, outputs=elements_to_reset, show_progress=False) + return indicator + components = [] component_dict = {} modules.shared.settings_components = component_dict @@ -1357,6 +1371,7 @@ def create_ui(): quicksettings_names = {x: i for i, x in enumerate(quicksettings_names) if x != 'quicksettings'} quicksettings_list = [] previous_section = None + tab_item_keys = [] current_tab = None current_row = None with gr.Tabs(elem_id="settings"): @@ -1365,6 +1380,8 @@ def create_ui(): if previous_section != item.section and not section_must_be_skipped: elem_id, text = item.section if current_tab is not None: + create_dirty_indicator(previous_section[0], tab_item_keys) + tab_item_keys = [] current_row.__exit__() current_tab.__exit__() current_tab = gr.TabItem(elem_id=f"settings_{elem_id}", label=text) @@ -1380,16 +1397,20 @@ def create_ui(): else: component = create_setting_component(k) component_dict[k] = component + tab_item_keys.append(k) components.append(component) if current_tab is not None: + create_dirty_indicator(previous_section[0], tab_item_keys) + tab_item_keys = [] current_row.__exit__() current_tab.__exit__() request_notifications = gr.Button(value='Request browser notifications', elem_id="request_notifications", visible=False) with gr.TabItem("Licenses", id="licenses", elem_id="settings_tab_licenses"): gr.HTML(modules.shared.html("licenses.html"), elem_id="licenses") + create_dirty_indicator("tab_licenses", [], interactive=False) with gr.TabItem("Show all pages", variant='primary', elem_id="settings_show_all_pages"): - pass + create_dirty_indicator("show_all_pages", [], interactive=False) def unload_sd_weights(): modules.sd_models.unload_model_weights() From 4c567b1f8414eccd8672089dea3e92636147037e Mon Sep 17 00:00:00 2001 From: Alex Heller Date: Thu, 8 Jun 2023 19:16:36 +0200 Subject: [PATCH 28/31] Exclude licenses from all settings. Also switches to the corresponding "show all settings" tab now for more consistency. --- javascript/ui.js | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/javascript/ui.js b/javascript/ui.js index fb9422c67..874e9f938 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -214,6 +214,19 @@ function updateOpts(json_string){ if (!meta.is_stored) unsaved.add(opt) }) } + +function showAllSettings() { + // Try to ensure that the show all settings tab is opened by clicking on its tab button + let tab_dirty_indicator = gradioApp().getElementById('modification_indicator_show_all_pages'); + if (tab_dirty_indicator && tab_dirty_indicator.nextSibling) { + tab_dirty_indicator.nextSibling.click(); + } + gradioApp().querySelectorAll('#settings > .tab-content > .tabitem').forEach((elem) => { + if (elem.id === 'settings_tab_licenses' || elem.id === 'settings_show_all_pages') return; + elem.style.display = 'block'; + }); +} + onUiUpdate(() => { sort_ui_elements(); if (Object.keys(opts).length !== 0) return; @@ -256,14 +269,10 @@ onUiUpdate(() => { registerTextarea('img2img_prompt', 'img2img_token_counter', 'img2img_token_button'); registerTextarea('img2img_neg_prompt', 'img2img_negative_token_counter', 'img2img_negative_token_button'); - // TODO: update to setting-improvements branch const settings_search = gradioApp().querySelectorAll('#settings_search > label > textarea')[0]; settings_search.oninput = (e) => { setTimeout(() => { - gradioApp().querySelectorAll('#settings > .tab-content > .tabitem').forEach((elem) => { - if (elem.id === 'settings_tab_licenses') return; - elem.style.display = 'block'; - }); + showAllSettings(); gradioApp().querySelectorAll('#tab_settings .tabitem').forEach((section) => { section.querySelectorAll('.dirtyable').forEach((setting) => { const visible = setting.innerText.toLowerCase().includes(e.target.value.toLowerCase()) || setting.id.toLowerCase().includes(e.target.value.toLowerCase()); @@ -312,7 +321,7 @@ onUiLoaded(function(){ return mut.type === 'attributes' && mut.attributeName === 'style' } if (mutations.some(mutation_on_style)) { - tab_elements.forEach((elem) => elem.style.display = "block") + showAllSettings(); } }) From ebabeab8c31b8f8a0e92568b188cdb610d094013 Mon Sep 17 00:00:00 2001 From: Alex Heller Date: Thu, 8 Jun 2023 19:31:59 +0200 Subject: [PATCH 29/31] Removed clipping when many settings changed --- javascript/style.css | 1 - 1 file changed, 1 deletion(-) diff --git a/javascript/style.css b/javascript/style.css index c14e19200..11dfe1e4f 100644 --- a/javascript/style.css +++ b/javascript/style.css @@ -293,7 +293,6 @@ div#extras_scale_to_tab div.form{ } #settings_result{ - height: 1.4em; margin: 0 1.2em; } From bb04bc5dc7b91af6d2373a7aca1ede0eab87eeeb Mon Sep 17 00:00:00 2001 From: Alex Heller Date: Fri, 9 Jun 2023 02:38:10 +0200 Subject: [PATCH 30/31] Reverted linendings to original file linendings --- javascript/style.css | 1284 +++++++++++++++--------------- modules/shared.py | 1766 +++++++++++++++++++++--------------------- 2 files changed, 1525 insertions(+), 1525 deletions(-) diff --git a/javascript/style.css b/javascript/style.css index bcba1d2f4..f8a28d227 100644 --- a/javascript/style.css +++ b/javascript/style.css @@ -1,642 +1,642 @@ -: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.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; } -.block.gradio-checkbox { margin: 0.75em 1.5em 0 0; } -.block.gradio-dropdown, .block.gradio-slider, .block.gradio-checkbox, .block.gradio-textbox, .block.gradio-radio, .block.gradio-checkboxgroup, .block.gradio-number, .block.gradio-colorpicker { border-width: 0 !important; box-shadow: none !important;} -.block.gradio-gallery{ background: var(--input-background-fill); } -.block.padded:not(.gradio-accordion) { padding: 0 !important; } -.compact{ background: transparent !important; padding: 0 !important; } -.dark .gradio-dropdown ul.options li.item:not(:has(.hide)) { background-color: var(--neutral-900); } -.gap.compact{ padding: 0; gap: 0.2em 0; } -.gradio-container .prose a, .gradio-container .prose a:visited{ color: unset; text-decoration: none; } -.gradio-dropdown .single-select{ white-space: nowrap; overflow: hidden; } -.gradio-dropdown .token-remove.remove-all.remove-all{ display: none; } -.gradio-dropdown div.wrap.wrap.wrap.wrap{ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); } -.gradio-dropdown label span:not(.has-info), .gradio-textbox label span:not(.has-info), .gradio-number label span:not(.has-info) { margin-bottom: 0; } -.gradio-dropdown ul.options li.item { padding: 0.05em 0; } -.gradio-dropdown ul.options li.item:not(:has(.hide)) { background-color: var(--neutral-100); } -.gradio-dropdown ul.options{ z-index: 3000; min-width: fit-content; max-width: inherit; white-space: nowrap; } -.gradio-dropdown:not(.multiselect) .wrap-inner.wrap-inner.wrap-inner{ flex-wrap: unset; } -.gradio-dropdown.multiselect .token-remove.remove-all.remove-all{ display: flex; } -.gradio-dropdown.multiselect div.wrap-inner { overflow-x: hidden; overflow-y: auto; max-height: 50vh; overflow-wrap: anywhere; } -.gradio-html div.wrap{ height: 100%; } -.gradio-slider input[type="number"]{ width: 6em; } -.hidden{ display: none; } - -/* general styled components */ -.gradio-button.tool{ max-width: 2.3em; min-width: 2.3em !important; height: 2.3em; align-self: end; line-height: 1em; border-radius: 0.5em; } -.gradio-button.secondary-down{ background: var(--button-secondary-background-fill); color: var(--button-secondary-text-color); } -.gradio-button.secondary-down, .gradio-button.secondary-down:hover{ box-shadow: 1px 1px 1px rgba(0,0,0,0.25) inset, 0px 0px 3px rgba(0,0,0,0.15) inset; } -.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); - font-weight: var(--button-large-text-weight); - border: var(--button-border-width) solid var(--button-secondary-border-color); - background: var(--button-secondary-background-fill); - color: var(--button-secondary-text-color); - font-size: var(--button-large-text-size); - display: inline-flex; - justify-content: center; - align-items: center; - transition: var(--button-transition); - box-shadow: var(--button-shadow); - text-align: center; -} - -/* remove footer */ -footer { display: none !important; } - -/* themes */ -.theme-preview { display: none; position: fixed; border: 4px solid var(--neutral-600); box-shadow: 2px 2px 2px 2px var(--neutral-700); top: 0; bottom: 0; left: 0; right: 0; margin: auto; max-width: 75vw; z-index: 999; } - -/* txt2img/img2img specific */ -.block.token-counter{ - position: absolute; - display: inline-block; - right: 1em; - min-width: 0 !important; - width: auto; - z-index: 100; - top: -0.75em; -} - -.block.token-counter span{ - background: var(--input-background-fill) !important; - box-shadow: 0 0 0.0 0.3em rgba(192,192,192,0.15), inset 0 0 0.6em rgba(192,192,192,0.075); - border: 2px solid rgba(192,192,192,0.4) !important; - border-radius: 0.4em; -} - -.block.token-counter.error span{ - box-shadow: 0 0 0.0 0.3em rgba(255,0,0,0.15), inset 0 0 0.6em rgba(255,0,0,0.075); - border: 2px solid rgba(255,0,0,0.4) !important; -} - -.block.token-counter div{ display: inline; } -.block.token-counter span{ padding: 0.1em 0.75em; } - -[id$=_subseed_show]{ - min-width: auto !important; - flex-grow: 0 !important; - display: flex; -} - -[id$=_subseed_show] label{ - margin-bottom: 0.5em; - align-self: end; -} - -.performance { font-size: 0.85em; color: #444; } -.performance p { display: inline-block; color: var(--body-text-color-subdued) !important } -.performance .time { margin-right: 0; } - -@media screen and (min-width: 2500px) { - #txt2img_gallery, #img2img_gallery { min-height: 768px; } -} - -#txt2img_gallery img, #img2img_gallery img, #extras_gallery img { object-fit: scale-down; width: -webkit-fill-available !important; } - -#txt2img_generate_box, #img2img_generate_box { gap: 0.5em; flex-wrap: wrap-reverse; } -#txt2img_actions_column, #img2img_actions_column { gap: 0.5em; } -#txt2img_generate_box > button, #img2img_generate_box > button { height: 2.2em; line-height: 0; } -#txt2img_generate_line2, #img2img_generate_line2 { display: flex; } -#txt2img_generate_line2 > button, #img2img_generate_line2 > button, #extras_generate_box > button { height: 2.2em; line-height: 0; min-width: unset; display: block !important; } -#txt2img_tools > div, #img2img_tools > div { justify-content: space-around; margin-top: 0.5em; margin-bottom: 0em; } -#txt2img_tools > div > button, #img2img_tools > div > button { scale: 120%; } -#refresh_txt2img_styles, #refresh_img2img_styles { height: 2.46em; margin-left: -8px; } - -.interrogate-col{ - min-width: 0 !important; - max-width: fit-content; - gap: 0.5em; -} -.interrogate-col > button{ - flex: 1; -} - -#txtimg_hr_finalres{ - min-height: 0 !important; - padding: .625rem .75rem; - margin-left: -0.75em -} - -#img2img_scale_resolution_preview.block{ - display: flex; - align-items: end; -} - -#txtimg_hr_finalres .resolution, #img2img_scale_resolution_preview .resolution{ - font-weight: bold; -} - -div#extras_scale_to_tab div.form{ - flex-direction: row; -} - -#img2img_column_batch{ - align-self: end; - margin-bottom: 0.9em; -} - -#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]{ - min-width: min(13.5em, 100%) !important; -} - -div.dimensions-tools{ - min-width: 0 !important; - max-width: fit-content; - flex-direction: row; - align-content: center; -} - -div#extras_scale_to_tab div.form{ - flex-direction: row; -} - -#mode_img2img .gradio-image > div.fixed-height, #mode_img2img .gradio-image > div.fixed-height img{ - height: 480px !important; - max-height: 480px !important; - min-height: 480px !important; -} - -#img2img_sketch, #img2maskimg, #inpaint_sketch { - overflow: overlay !important; - resize: auto; - background: var(--panel-background-fill); - z-index: 5; -} - -.image-buttons button{ - min-width: auto; -} - -.infotext { - overflow-wrap: break-word; -} - -/* settings */ -#quicksettings { - width: fit-content; - align-items: end; -} - -#quicksettings > div, #quicksettings > fieldset{ - max-width: 24em; - min-width: 24em; - padding: 0; - border: none; - box-shadow: none; - background: none; -} - -#settings{ - display: flex; - flex-flow: row wrap; - gap: var(--layout-gap); -} - -#settings div { - border: none; -} - -#settings > div.tab-content { - flex: 100000 0 75%; -} - -#settings > div.tab-content > div{ - border: none; - padding: 0; -} - -#settings > div.tab-nav{ - display: grid; - grid-template-columns: repeat(auto-fill, .3em minmax(10em, 1fr)); - flex: 1 0 auto; - width: 11em; - align-self: flex-start; - gap: var(--spacing-md); -} - -#settings > div.tab-nav button{ - display: block; - border: none; - text-align: left; - white-space: initial; - padding: 0; -} - -#settings > div.tab-nav > #settings_show_all_pages { - padding: var(--size-2) var(--size-4); -} - -#settings .block.gradio-checkbox { - margin: 0; - width: auto; -} - -#settings .dirtyable { - display: grid; - grid-template-columns: .3em auto; - gap: .5em; -} - -#settings .dirtyable.hidden { - display: none; -} - -#settings .modification-indicator { - width: 100%; - height: 100%; - border-radius: 1em !important; - padding: 0; -} - -#settings .modification-indicator:disabled { - visibility: hidden; -} - -#settings .modification-indicator.unsaved { - background: var(--color-accent-soft); -} - -#settings .modification-indicator.changed { - background: var(--color-accent); -} - -#settings .modification-indicator.changed.unsaved { - background-image: linear-gradient(var(--color-accent) 25%, var(--color-accent-soft) 75%); -} - -#settings_result{ - margin: 0 1.2em; -} - - -/* live preview */ -.progressDiv{ - position: relative; - height: 20px; - background: #b4c0cc; - border-radius: 3px !important; - margin-bottom: -3px; -} - -.dark .progressDiv{ - background: #424c5b; -} - -.progressDiv .progress{ - width: 0%; - height: 20px; - background: #0060df; - color: white; - font-weight: bold; - line-height: 20px; - padding: 0 8px 0 0; - text-align: right; - border-radius: 3px; - overflow: visible; - white-space: nowrap; - 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; -} - -.global-popup{ - display: flex; - position: fixed; - z-index: 1001; - left: 0; - top: 0; - width: 100%; - height: 100%; - overflow: auto; - background-color: rgba(20, 20, 20, 0.95); -} - - -.global-popup-close:before { - content: "×"; -} - -.global-popup-close{ - position: fixed; - right: 0.25em; - top: 0; - cursor: pointer; - color: white; - font-size: 32pt; -} - -.global-popup-inner{ - display: inline-block; - margin: auto; - padding: 2em; -} - -/* fullpage image viewer */ - -#lightboxModal{ - display: none; - position: fixed; - z-index: 1001; - left: 0; - top: 0; - width: 100%; - height: 100%; - overflow: auto; - background-color: rgba(20, 20, 20, 0.95); - user-select: none; - -webkit-user-select: none; - flex-direction: column; -} - -.modalControls { - display: flex; - gap: 1em; - padding: 1em; - background-color: rgba(0,0,0,0.2); - position: absolute; - width: fit-content; - z-index: 1; -} - -.modalClose { - margin-left: auto; -} - -.modalControls span{ - color: white; - font-size: 35px; - font-weight: bold; - cursor: pointer; - width: 1em; -} - -.modalControls span:hover, .modalControls span:focus{ - color: #999; - text-decoration: none; -} - -#lightboxModal > img { - display: block; - margin: auto; - width: auto; -} - -#lightboxModal > img.modalImageFullscreen{ - object-fit: contain; - height: 100%; - width: 100%; - min-height: 0; -} - -table.settings-value-table{ - background: white; - border-collapse: collapse; - margin: 1em; - border: 4px solid white; -} - -table.settings-value-table td{ - padding: 0.4em; - border: 1px solid #ccc; - max-width: 36em; -} - -.modalPrev, .modalNext { - cursor: pointer; - position: absolute; - top: 0; - width: auto; - height: 100vh; - line-height: 100vh; - text-align: center; - padding: 16px; - margin-top: -50px; - color: white; - font-weight: bold; - font-size: 20px; - transition: 0.6s ease; - border-radius: 0 3px 3px 0; - user-select: none; - -webkit-user-select: none; -} - -.modalNext { - right: 0; - border-radius: 3px 0 0 3px; -} - -.modalPrev:hover, .modalNext:hover { - background-color: rgba(0, 0, 0, 0.8); -} - -#imageARPreview { - position: absolute; - top: 0px; - left: 0px; - border: 2px solid red; - background: rgba(255, 0, 0, 0.3); - z-index: 900; - pointer-events: none; - display: none; -} - -/* context menu (ie for the generate button) */ - -#context-menu{ - z-index:9999; - position:absolute; - display:block; - padding:0px 0; - border:2px solid #a55000; - border-radius:8px; - box-shadow:1px 1px 2px #CE6400; - width: 200px; -} - -.context-menu-items{ - list-style: none; - margin: 0; - padding: 0; -} - -.context-menu-items a{ - display:block; - padding:5px; - cursor:pointer; -} - -.context-menu-items a:hover{ - background: #a55000; -} - - -/* 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; } -#tab_extensions button{ max-width: 16em; } -#tab_extensions input[disabled="disabled"]{ opacity: 0.5; } -.extension-tag{ font-weight: bold; font-size: 95%; } -#extensions .name{ font-size: 1.1rem } -#extensions .type{ opacity: 0.5; font-size: 90%; text-align: center; } -#extensions .version{ opacity: 0.7; } -#extensions .info{ margin: 0; } -#extensions .date{ opacity: 0.85; font-size: 90%; } -.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; } -.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; } - -/* 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)); } -fieldset.controlnet_resize_mode_radio .wrap:last-of-type, fieldset.controlnet_control_mode_radio .wrap:last-of-type { flex-direction: column; } -div.controlnet_preprocessor_model { display: grid; grid-auto-flow: row; grid-template-columns: 1fr max-content; } -div.controlnet_preprocessor_model button.gradio-button { align-self: center; } -div.controlnet_weight_steps > div.form { display: grid; grid-template: repeat(2, 1fr) / repeat(2, 1fr); } -div.controlnet_weight_steps .controlnet_control_weight_slider { grid-column: 1 / -1; } -div.controlnet_image_controls { display: grid; grid-template-columns: repeat(4, 1fr); } -div.controlnet_image_controls .controlnet_invert_warning { grid-column: 1 / -1; } -div.controlnet_image_controls button { justify-self: center; } -div.controlnet_main_options { display: grid; grid-template-columns: 1fr 1fr; grid-auto-flow: row; } - -/* specific elements */ -#modelmerger_interp_description { margin-top: 1em; margin-bottom: 1em; } -#scripts_alwayson_txt2img, #scripts_alwayson_img2img { display: grid } -#extras_generate, #extras_interrupt, #extras_skip { display: block !important; position: relative; height: 36px; } -#extras_upscale { margin-top: 10px } -#refresh_tac_refreshTempFiles { display: none; } +: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.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; } +.block.gradio-checkbox { margin: 0.75em 1.5em 0 0; } +.block.gradio-dropdown, .block.gradio-slider, .block.gradio-checkbox, .block.gradio-textbox, .block.gradio-radio, .block.gradio-checkboxgroup, .block.gradio-number, .block.gradio-colorpicker { border-width: 0 !important; box-shadow: none !important;} +.block.gradio-gallery{ background: var(--input-background-fill); } +.block.padded:not(.gradio-accordion) { padding: 0 !important; } +.compact{ background: transparent !important; padding: 0 !important; } +.dark .gradio-dropdown ul.options li.item:not(:has(.hide)) { background-color: var(--neutral-900); } +.gap.compact{ padding: 0; gap: 0.2em 0; } +.gradio-container .prose a, .gradio-container .prose a:visited{ color: unset; text-decoration: none; } +.gradio-dropdown .single-select{ white-space: nowrap; overflow: hidden; } +.gradio-dropdown .token-remove.remove-all.remove-all{ display: none; } +.gradio-dropdown div.wrap.wrap.wrap.wrap{ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); } +.gradio-dropdown label span:not(.has-info), .gradio-textbox label span:not(.has-info), .gradio-number label span:not(.has-info) { margin-bottom: 0; } +.gradio-dropdown ul.options li.item { padding: 0.05em 0; } +.gradio-dropdown ul.options li.item:not(:has(.hide)) { background-color: var(--neutral-100); } +.gradio-dropdown ul.options{ z-index: 3000; min-width: fit-content; max-width: inherit; white-space: nowrap; } +.gradio-dropdown:not(.multiselect) .wrap-inner.wrap-inner.wrap-inner{ flex-wrap: unset; } +.gradio-dropdown.multiselect .token-remove.remove-all.remove-all{ display: flex; } +.gradio-dropdown.multiselect div.wrap-inner { overflow-x: hidden; overflow-y: auto; max-height: 50vh; overflow-wrap: anywhere; } +.gradio-html div.wrap{ height: 100%; } +.gradio-slider input[type="number"]{ width: 6em; } +.hidden{ display: none; } + +/* general styled components */ +.gradio-button.tool{ max-width: 2.3em; min-width: 2.3em !important; height: 2.3em; align-self: end; line-height: 1em; border-radius: 0.5em; } +.gradio-button.secondary-down{ background: var(--button-secondary-background-fill); color: var(--button-secondary-text-color); } +.gradio-button.secondary-down, .gradio-button.secondary-down:hover{ box-shadow: 1px 1px 1px rgba(0,0,0,0.25) inset, 0px 0px 3px rgba(0,0,0,0.15) inset; } +.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); + font-weight: var(--button-large-text-weight); + border: var(--button-border-width) solid var(--button-secondary-border-color); + background: var(--button-secondary-background-fill); + color: var(--button-secondary-text-color); + font-size: var(--button-large-text-size); + display: inline-flex; + justify-content: center; + align-items: center; + transition: var(--button-transition); + box-shadow: var(--button-shadow); + text-align: center; +} + +/* remove footer */ +footer { display: none !important; } + +/* themes */ +.theme-preview { display: none; position: fixed; border: 4px solid var(--neutral-600); box-shadow: 2px 2px 2px 2px var(--neutral-700); top: 0; bottom: 0; left: 0; right: 0; margin: auto; max-width: 75vw; z-index: 999; } + +/* txt2img/img2img specific */ +.block.token-counter{ + position: absolute; + display: inline-block; + right: 1em; + min-width: 0 !important; + width: auto; + z-index: 100; + top: -0.75em; +} + +.block.token-counter span{ + background: var(--input-background-fill) !important; + box-shadow: 0 0 0.0 0.3em rgba(192,192,192,0.15), inset 0 0 0.6em rgba(192,192,192,0.075); + border: 2px solid rgba(192,192,192,0.4) !important; + border-radius: 0.4em; +} + +.block.token-counter.error span{ + box-shadow: 0 0 0.0 0.3em rgba(255,0,0,0.15), inset 0 0 0.6em rgba(255,0,0,0.075); + border: 2px solid rgba(255,0,0,0.4) !important; +} + +.block.token-counter div{ display: inline; } +.block.token-counter span{ padding: 0.1em 0.75em; } + +[id$=_subseed_show]{ + min-width: auto !important; + flex-grow: 0 !important; + display: flex; +} + +[id$=_subseed_show] label{ + margin-bottom: 0.5em; + align-self: end; +} + +.performance { font-size: 0.85em; color: #444; } +.performance p { display: inline-block; color: var(--body-text-color-subdued) !important } +.performance .time { margin-right: 0; } + +@media screen and (min-width: 2500px) { + #txt2img_gallery, #img2img_gallery { min-height: 768px; } +} + +#txt2img_gallery img, #img2img_gallery img, #extras_gallery img { object-fit: scale-down; width: -webkit-fill-available !important; } + +#txt2img_generate_box, #img2img_generate_box { gap: 0.5em; flex-wrap: wrap-reverse; } +#txt2img_actions_column, #img2img_actions_column { gap: 0.5em; } +#txt2img_generate_box > button, #img2img_generate_box > button { height: 2.2em; line-height: 0; } +#txt2img_generate_line2, #img2img_generate_line2 { display: flex; } +#txt2img_generate_line2 > button, #img2img_generate_line2 > button, #extras_generate_box > button { height: 2.2em; line-height: 0; min-width: unset; display: block !important; } +#txt2img_tools > div, #img2img_tools > div { justify-content: space-around; margin-top: 0.5em; margin-bottom: 0em; } +#txt2img_tools > div > button, #img2img_tools > div > button { scale: 120%; } +#refresh_txt2img_styles, #refresh_img2img_styles { height: 2.46em; margin-left: -8px; } + +.interrogate-col{ + min-width: 0 !important; + max-width: fit-content; + gap: 0.5em; +} +.interrogate-col > button{ + flex: 1; +} + +#txtimg_hr_finalres{ + min-height: 0 !important; + padding: .625rem .75rem; + margin-left: -0.75em +} + +#img2img_scale_resolution_preview.block{ + display: flex; + align-items: end; +} + +#txtimg_hr_finalres .resolution, #img2img_scale_resolution_preview .resolution{ + font-weight: bold; +} + +div#extras_scale_to_tab div.form{ + flex-direction: row; +} + +#img2img_column_batch{ + align-self: end; + margin-bottom: 0.9em; +} + +#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]{ + min-width: min(13.5em, 100%) !important; +} + +div.dimensions-tools{ + min-width: 0 !important; + max-width: fit-content; + flex-direction: row; + align-content: center; +} + +div#extras_scale_to_tab div.form{ + flex-direction: row; +} + +#mode_img2img .gradio-image > div.fixed-height, #mode_img2img .gradio-image > div.fixed-height img{ + height: 480px !important; + max-height: 480px !important; + min-height: 480px !important; +} + +#img2img_sketch, #img2maskimg, #inpaint_sketch { + overflow: overlay !important; + resize: auto; + background: var(--panel-background-fill); + z-index: 5; +} + +.image-buttons button{ + min-width: auto; +} + +.infotext { + overflow-wrap: break-word; +} + +/* settings */ +#quicksettings { + width: fit-content; + align-items: end; +} + +#quicksettings > div, #quicksettings > fieldset{ + max-width: 24em; + min-width: 24em; + padding: 0; + border: none; + box-shadow: none; + background: none; +} + +#settings{ + display: flex; + flex-flow: row wrap; + gap: var(--layout-gap); +} + +#settings div { + border: none; +} + +#settings > div.tab-content { + flex: 100000 0 75%; +} + +#settings > div.tab-content > div{ + border: none; + padding: 0; +} + +#settings > div.tab-nav{ + display: grid; + grid-template-columns: repeat(auto-fill, .3em minmax(10em, 1fr)); + flex: 1 0 auto; + width: 11em; + align-self: flex-start; + gap: var(--spacing-md); +} + +#settings > div.tab-nav button{ + display: block; + border: none; + text-align: left; + white-space: initial; + padding: 0; +} + +#settings > div.tab-nav > #settings_show_all_pages { + padding: var(--size-2) var(--size-4); +} + +#settings .block.gradio-checkbox { + margin: 0; + width: auto; +} + +#settings .dirtyable { + display: grid; + grid-template-columns: .3em auto; + gap: .5em; +} + +#settings .dirtyable.hidden { + display: none; +} + +#settings .modification-indicator { + width: 100%; + height: 100%; + border-radius: 1em !important; + padding: 0; +} + +#settings .modification-indicator:disabled { + visibility: hidden; +} + +#settings .modification-indicator.unsaved { + background: var(--color-accent-soft); +} + +#settings .modification-indicator.changed { + background: var(--color-accent); +} + +#settings .modification-indicator.changed.unsaved { + background-image: linear-gradient(var(--color-accent) 25%, var(--color-accent-soft) 75%); +} + +#settings_result{ + margin: 0 1.2em; +} + + +/* live preview */ +.progressDiv{ + position: relative; + height: 20px; + background: #b4c0cc; + border-radius: 3px !important; + margin-bottom: -3px; +} + +.dark .progressDiv{ + background: #424c5b; +} + +.progressDiv .progress{ + width: 0%; + height: 20px; + background: #0060df; + color: white; + font-weight: bold; + line-height: 20px; + padding: 0 8px 0 0; + text-align: right; + border-radius: 3px; + overflow: visible; + white-space: nowrap; + 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; +} + +.global-popup{ + display: flex; + position: fixed; + z-index: 1001; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgba(20, 20, 20, 0.95); +} + + +.global-popup-close:before { + content: "×"; +} + +.global-popup-close{ + position: fixed; + right: 0.25em; + top: 0; + cursor: pointer; + color: white; + font-size: 32pt; +} + +.global-popup-inner{ + display: inline-block; + margin: auto; + padding: 2em; +} + +/* fullpage image viewer */ + +#lightboxModal{ + display: none; + position: fixed; + z-index: 1001; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgba(20, 20, 20, 0.95); + user-select: none; + -webkit-user-select: none; + flex-direction: column; +} + +.modalControls { + display: flex; + gap: 1em; + padding: 1em; + background-color: rgba(0,0,0,0.2); + position: absolute; + width: fit-content; + z-index: 1; +} + +.modalClose { + margin-left: auto; +} + +.modalControls span{ + color: white; + font-size: 35px; + font-weight: bold; + cursor: pointer; + width: 1em; +} + +.modalControls span:hover, .modalControls span:focus{ + color: #999; + text-decoration: none; +} + +#lightboxModal > img { + display: block; + margin: auto; + width: auto; +} + +#lightboxModal > img.modalImageFullscreen{ + object-fit: contain; + height: 100%; + width: 100%; + min-height: 0; +} + +table.settings-value-table{ + background: white; + border-collapse: collapse; + margin: 1em; + border: 4px solid white; +} + +table.settings-value-table td{ + padding: 0.4em; + border: 1px solid #ccc; + max-width: 36em; +} + +.modalPrev, .modalNext { + cursor: pointer; + position: absolute; + top: 0; + width: auto; + height: 100vh; + line-height: 100vh; + text-align: center; + padding: 16px; + margin-top: -50px; + color: white; + font-weight: bold; + font-size: 20px; + transition: 0.6s ease; + border-radius: 0 3px 3px 0; + user-select: none; + -webkit-user-select: none; +} + +.modalNext { + right: 0; + border-radius: 3px 0 0 3px; +} + +.modalPrev:hover, .modalNext:hover { + background-color: rgba(0, 0, 0, 0.8); +} + +#imageARPreview { + position: absolute; + top: 0px; + left: 0px; + border: 2px solid red; + background: rgba(255, 0, 0, 0.3); + z-index: 900; + pointer-events: none; + display: none; +} + +/* context menu (ie for the generate button) */ + +#context-menu{ + z-index:9999; + position:absolute; + display:block; + padding:0px 0; + border:2px solid #a55000; + border-radius:8px; + box-shadow:1px 1px 2px #CE6400; + width: 200px; +} + +.context-menu-items{ + list-style: none; + margin: 0; + padding: 0; +} + +.context-menu-items a{ + display:block; + padding:5px; + cursor:pointer; +} + +.context-menu-items a:hover{ + background: #a55000; +} + + +/* 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; } +#tab_extensions button{ max-width: 16em; } +#tab_extensions input[disabled="disabled"]{ opacity: 0.5; } +.extension-tag{ font-weight: bold; font-size: 95%; } +#extensions .name{ font-size: 1.1rem } +#extensions .type{ opacity: 0.5; font-size: 90%; text-align: center; } +#extensions .version{ opacity: 0.7; } +#extensions .info{ margin: 0; } +#extensions .date{ opacity: 0.85; font-size: 90%; } +.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; } +.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; } + +/* 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)); } +fieldset.controlnet_resize_mode_radio .wrap:last-of-type, fieldset.controlnet_control_mode_radio .wrap:last-of-type { flex-direction: column; } +div.controlnet_preprocessor_model { display: grid; grid-auto-flow: row; grid-template-columns: 1fr max-content; } +div.controlnet_preprocessor_model button.gradio-button { align-self: center; } +div.controlnet_weight_steps > div.form { display: grid; grid-template: repeat(2, 1fr) / repeat(2, 1fr); } +div.controlnet_weight_steps .controlnet_control_weight_slider { grid-column: 1 / -1; } +div.controlnet_image_controls { display: grid; grid-template-columns: repeat(4, 1fr); } +div.controlnet_image_controls .controlnet_invert_warning { grid-column: 1 / -1; } +div.controlnet_image_controls button { justify-self: center; } +div.controlnet_main_options { display: grid; grid-template-columns: 1fr 1fr; grid-auto-flow: row; } + +/* specific elements */ +#modelmerger_interp_description { margin-top: 1em; margin-bottom: 1em; } +#scripts_alwayson_txt2img, #scripts_alwayson_img2img { display: grid } +#extras_generate, #extras_interrupt, #extras_skip { display: block !important; position: relative; height: 36px; } +#extras_upscale { margin-top: 10px } +#refresh_tac_refreshTempFiles { display: none; } diff --git a/modules/shared.py b/modules/shared.py index 3548fc439..04fc3c994 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -1,883 +1,883 @@ -import os -import sys -import time -import json -import datetime -import urllib.request -from enum import Enum -import gradio as gr -import tqdm -import requests -from modules import errors, ui_components, shared_items, cmd_args -from modules.paths_internal import models_path, script_path, data_path, sd_configs_path, sd_default_config, sd_model_file, default_sd_model_file, extensions_dir, extensions_builtin_dir # pylint: disable=W0611 -import modules.interrogate -import modules.memmon -import modules.styles -import modules.devices as devices -import modules.paths_internal as paths -from installer import log as central_logger # pylint: disable=E0611 - - -errors.install(gr) -demo: gr.Blocks = None -log = central_logger -progress_print_out = sys.stdout -parser = cmd_args.parser -url = 'https://github.com/vladmandic/automatic' -cmd_opts, _ = parser.parse_known_args() -hide_dirs = {"visible": not cmd_opts.hide_ui_dir_config} -xformers_available = False -clip_model = None -interrogator = modules.interrogate.InterrogateModels("interrogate") -sd_upscalers = [] -face_restorers = [] -tab_names = [] -options_templates = {} -hypernetworks = {} -loaded_hypernetworks = [] -gradio_theme = gr.themes.Base() -settings_components = None -latent_upscale_default_mode = "Latent" -latent_upscale_modes = { - "Latent": {"mode": "bilinear", "antialias": False}, - "Latent (antialiased)": {"mode": "bilinear", "antialias": True}, - "Latent (bicubic)": {"mode": "bicubic", "antialias": False}, - "Latent (bicubic antialiased)": {"mode": "bicubic", "antialias": True}, - "Latent (nearest)": {"mode": "nearest", "antialias": False}, - "Latent (nearest-exact)": {"mode": "nearest-exact", "antialias": False}, -} -restricted_opts = { - "samples_filename_pattern", - "directories_filename_pattern", - "outdir_samples", - "outdir_txt2img_samples", - "outdir_img2img_samples", - "outdir_extras_samples", - "outdir_grids", - "outdir_txt2img_grids", - "outdir_save", - "outdir_init_images" -} -ui_reorder_categories = [ - "inpaint", - "sampler", - "checkboxes", - "hires_fix", - "dimensions", - "cfg", - "seed", - "batch", - "override_settings", - "scripts", -] - - -class Backend(Enum): - ORIGINAL = 1 - DIFFUSERS = 2 - - -def reload_hypernetworks(): - from modules.hypernetworks import hypernetwork - global hypernetworks # pylint: disable=W0603 - hypernetworks = hypernetwork.list_hypernetworks(opts.hypernetwork_dir) - - -class State: - skipped = False - interrupted = False - paused = False - job = "" - job_no = 0 - job_count = 0 - processing_has_refined_job_count = False - job_timestamp = '0' - sampling_step = 0 - sampling_steps = 0 - current_latent = None - current_image = None - current_image_sampling_step = 0 - id_live_preview = 0 - textinfo = None - time_start = None - need_restart = False - server_start = None - oom = False - - def skip(self): - log.debug('Requested skip') - self.skipped = True - - def interrupt(self): - log.debug('Requested interrupt') - self.interrupted = True - - def pause(self): - self.paused = not self.paused - log.debug(f'Requested {"pause" if self.paused else "continue"}') - - def nextjob(self): - if opts.live_previews_enable and opts.show_progress_every_n_steps == -1: - self.do_set_current_image() - self.job_no += 1 - self.sampling_step = 0 - self.current_image_sampling_step = 0 - - def dict(self): - obj = { - "skipped": self.skipped, - "interrupted": self.interrupted, - "job": self.job, - "job_count": self.job_count, - "job_timestamp": self.job_timestamp, - "job_no": self.job_no, - "sampling_step": self.sampling_step, - "sampling_steps": self.sampling_steps, - } - return obj - - def begin(self): - self.sampling_step = 0 - self.job_count = -1 - self.processing_has_refined_job_count = False - self.job_no = 0 - self.job_timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S") - self.current_latent = None - self.current_image = None - self.current_image_sampling_step = 0 - self.id_live_preview = 0 - self.skipped = False - self.interrupted = False - self.paused = False - self.textinfo = None - self.time_start = time.time() - devices.torch_gc() - - def end(self): - self.job = "" - self.job_count = 0 - self.paused = False - devices.torch_gc() - - def set_current_image(self): - """sets self.current_image from self.current_latent if enough sampling steps have been made after the last call to this""" - if not parallel_processing_allowed: - return - if self.sampling_step - self.current_image_sampling_step >= opts.show_progress_every_n_steps and opts.live_previews_enable and opts.show_progress_every_n_steps != -1: - self.do_set_current_image() - - def do_set_current_image(self): - if self.current_latent is None: - return - import modules.sd_samplers # pylint: disable=W0621 - if opts.show_progress_grid: - self.assign_current_image(modules.sd_samplers.samples_to_image_grid(self.current_latent)) - else: - self.assign_current_image(modules.sd_samplers.sample_to_image(self.current_latent)) - self.current_image_sampling_step = self.sampling_step - - def assign_current_image(self, image): - self.current_image = image - self.id_live_preview += 1 - -state = State() -state.server_start = time.time() - - -class OptionInfo: - def __init__(self, default=None, label="", component=None, component_args=None, onchange=None, section=None, refresh=None, comment_before='', comment_after=''): - self.default = default - self.label = label - self.component = component - self.component_args = component_args - self.onchange = onchange - self.section = section - self.refresh = refresh - self.comment_before = comment_before # HTML text that will be added after label in UI - self.comment_after = comment_after # HTML text that will be added before label in UI - - def link(self, label, uri): - self.comment_before += f"[{label}]" - return self - - def js(self, label, js_func): - self.comment_before += f"[{label}]" - return self - - def info(self, info): - self.comment_after += f"({info})" - return self - - def needs_restart(self): - self.comment_after += " (requires restart)" - return self - - -def options_section(section_identifier, options_dict): - for v in options_dict.values(): - v.section = section_identifier - return options_dict - - -def list_checkpoint_tiles(): - import modules.sd_models # pylint: disable=W0621 - return modules.sd_models.checkpoint_tiles() - - -default_checkpoint = list_checkpoint_tiles()[0] if len(list_checkpoint_tiles()) > 0 else "model.ckpt" - - -def refresh_checkpoints(): - import modules.sd_models # pylint: disable=W0621 - return modules.sd_models.list_models() - - -def list_samplers(): - import modules.sd_samplers # pylint: disable=W0621 - modules.sd_samplers.set_samplers() - return modules.sd_samplers.all_samplers - -def list_themes(): - fn = os.path.join('html', 'themes.json') - if not os.path.exists(fn): - refresh_themes() - if os.path.exists(fn): - with open(fn, mode='r', encoding='utf=8') as f: - res = json.loads(f.read()) - else: - res = [] - builtin = ["black-orange", "gradio/default", "gradio/base", "gradio/glass", "gradio/monochrome", "gradio/soft"] - themes = sorted(set(builtin + [x['id'] for x in res if x['status'] == 'RUNNING' and 'test' not in x['id'].lower()]), key=str.casefold) - return themes - - -def lora_disable(): - if opts.lora_disable: - if 'Lora' not in opts.disabled_extensions: - opts.data['disabled_extensions'].append('Lora') - else: - opts.data['disabled_extensions'] = [x for x in opts.disabled_extensions if x != 'Lora'] - - -def refresh_themes(): - try: - req = requests.get('https://huggingface.co/datasets/freddyaboulton/gradio-theme-subdomains/resolve/main/subdomains.json', timeout=5) - if req.status_code == 200: - res = req.json() - fn = os.path.join('html', 'themes.json') - with open(fn, mode='w', encoding='utf=8') as f: - f.write(json.dumps(res)) - else: - log.error('Error refreshing UI themes') - except: - log.error('Exception refreshing UI themes') - - -if devices.backend == "cpu": - cross_attention_optimization_default = "Doggettx's" -elif devices.backend == "mps": - cross_attention_optimization_default = "Doggettx's" -elif devices.backend == "ipex": - cross_attention_optimization_default = "InvokeAI's" -elif devices.backend == "directml": - cross_attention_optimization_default = "Sub-quadratic" -elif devices.backend == "rocm": - cross_attention_optimization_default = "Sub-quadratic" -else: # cuda - cross_attention_optimization_default ="Scaled-Dot-Product" - -options_templates.update(options_section(('sd', "Stable Diffusion"), { - "sd_model_checkpoint": OptionInfo(default_checkpoint, "Stable Diffusion checkpoint", gr.Dropdown, lambda: {"choices": list_checkpoint_tiles()}, refresh=refresh_checkpoints), - "sd_checkpoint_cache": OptionInfo(0, "Number of cached model checkpoints", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), - "sd_vae_checkpoint_cache": OptionInfo(0, "Number of cached VAE checkpoints", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), - "sd_vae": OptionInfo("Automatic", "Select VAE", gr.Dropdown, lambda: {"choices": shared_items.sd_vae_items()}, refresh=shared_items.refresh_vae_list), - "sd_model_dict": OptionInfo('None', "Stable Diffusion checkpoint dict", gr.Dropdown, lambda: {"choices": ['None'] + list_checkpoint_tiles()}, refresh=refresh_checkpoints), - "sd_vae_sliced_encode": OptionInfo(False, "Enable splitting of hires batch processing"), - "stream_load": OptionInfo(False, "When loading models attempt stream loading optimized for slow or network storage"), - "model_reuse_dict": OptionInfo(False, "When loading models attempt to reuse previous model dictionary"), - "cross_attention_optimization": OptionInfo(cross_attention_optimization_default, "Cross-attention optimization method", gr.Radio, lambda: {"choices": shared_items.list_crossattention() }), - "cross_attention_options": OptionInfo([], "Cross-attention advanced options", gr.CheckboxGroup, lambda: {"choices": ['xFormers enable flash Attention', 'SDP disable memory attention']}), - "sub_quad_q_chunk_size": OptionInfo(512, "Sub-quadratic cross-attention query chunk size", gr.Slider, {"minimum": 16, "maximum": 8192, "step": 8}), - "sub_quad_kv_chunk_size": OptionInfo(512, "Sub-quadratic cross-attention kv chunk size", gr.Slider, {"minimum": 0, "maximum": 8192, "step": 8}), - "sub_quad_chunk_threshold": OptionInfo(80, "Sub-quadratic cross-attention chunking threshold", gr.Slider, {"minimum": 0, "maximum": 100, "step": 1}), - "prompt_attention": OptionInfo("Full parser", "Prompt attention parser", gr.Radio, lambda: {"choices": ["Full parser", "Compel parser", "A1111 parser", "Fixed attention"] }), - "prompt_mean_norm": OptionInfo(True, "Prompt attention mean normalization"), - "always_batch_cond_uncond": OptionInfo(False, "Disable conditional batching enabled on low memory systems"), - "enable_quantization": OptionInfo(True, "Enable samplers quantization for sharper and cleaner results"), - "comma_padding_backtrack": OptionInfo(20, "Increase coherency by padding from the last comma within n tokens when using more than 75 tokens", gr.Slider, {"minimum": 0, "maximum": 74, "step": 1 }), - "sd_backend": OptionInfo("Original", "Stable Diffusion backend (experimental)", gr.Radio, lambda: {"choices": ["Original", "Diffusers"] }), -})) - -options_templates.update(options_section(('cuda', "Compute Settings"), { - "memmon_poll_rate": OptionInfo(2, "VRAM usage polls per second during generation", gr.Slider, {"minimum": 0, "maximum": 40, "step": 1}), - "precision": OptionInfo("Autocast", "Precision type", gr.Radio, lambda: {"choices": ["Autocast", "Full"]}), - "cuda_dtype": OptionInfo("FP32" if sys.platform == "darwin" else "FP16", "Device precision type", gr.Radio, lambda: {"choices": ["FP32", "FP16", "BF16"]}), - "no_half": OptionInfo(False, "Use full precision for model (--no-half)", None, None, None), - "no_half_vae": OptionInfo(False, "Use full precision for VAE (--no-half-vae)"), - "upcast_sampling": OptionInfo(True if sys.platform == "darwin" else False, "Enable upcast sampling"), - "upcast_attn": OptionInfo(False, "Enable upcast cross attention layer"), - "disable_nan_check": OptionInfo(True, "Disable NaN check in produced images/latent spaces"), - "rollback_vae": OptionInfo(False, "Attempt to roll back VAE when produced NaN values, requires NaN check (experimental)"), - "opt_channelslast": OptionInfo(False, "Use channels last as torch memory format "), - "cudnn_benchmark": OptionInfo(False, "Enable full-depth cuDNN benchmark feature"), - "cuda_allow_tf32": OptionInfo(True, "Allow TF32 math ops"), - "cuda_allow_tf16_reduced": OptionInfo(True, "Allow TF16 reduced precision math ops"), - "cuda_compile": OptionInfo(False, "Enable model compile (experimental)"), - "cuda_compile_mode": OptionInfo("none", "Model compile mode (experimental)", gr.Radio, lambda: {"choices": ['none', 'inductor', 'cudagraphs', 'aot_ts_nvfuser', 'hidet', 'ipex']}), - "cuda_compile_verbose": OptionInfo(False, "Model compile verbose mode"), - "cuda_compile_errors": OptionInfo(True, "Model compile suppress errors"), - "disable_gc": OptionInfo(False, "Disable Torch memory garbage collection (experimental)"), -})) - -options_templates.update(options_section(('system-paths', "System Paths"), { - "temp_dir": OptionInfo("", "Directory for temporary images; leave empty for default"), - "clean_temp_dir_at_start": OptionInfo(True, "Cleanup non-default temporary directory when starting webui"), - "ckpt_dir": OptionInfo(os.path.join(paths.models_path, 'Stable-diffusion'), "Path to directory with stable diffusion checkpoints"), - "diffusers_dir": OptionInfo(os.path.join(paths.models_path, 'Diffusers'), "Path to directory with stable diffusion diffusers"), - "vae_dir": OptionInfo(os.path.join(paths.models_path, 'VAE'), "Path to directory with VAE files"), - "lora_dir": OptionInfo(os.path.join(paths.models_path, 'Lora'), "Path to directory with Lora network(s)"), - "lyco_dir": OptionInfo(os.path.join(paths.models_path, 'LyCORIS'), "Path to directory with LyCORIS network(s)"), - "styles_dir": OptionInfo(os.path.join(paths.data_path, 'styles.csv'), "Path to user-defined styles file"), - "embeddings_dir": OptionInfo(os.path.join(paths.models_path, 'embeddings'), "Embeddings directory for textual inversion"), - "hypernetwork_dir": OptionInfo(os.path.join(paths.models_path, 'hypernetworks'), "Hypernetwork directory"), - "codeformer_models_path": OptionInfo(os.path.join(paths.models_path, 'Codeformer'), "Path to directory with codeformer model file(s)"), - "gfpgan_models_path": OptionInfo(os.path.join(paths.models_path, 'GFPGAN'), "Path to directory with GFPGAN model file(s)"), - "esrgan_models_path": OptionInfo(os.path.join(paths.models_path, 'ESRGAN'), "Path to directory with ESRGAN model file(s)"), - "bsrgan_models_path": OptionInfo(os.path.join(paths.models_path, 'BSRGAN'), "Path to directory with BSRGAN model file(s)"), - "realesrgan_models_path": OptionInfo(os.path.join(paths.models_path, 'RealESRGAN'), "Path to directory with RealESRGAN model file(s)"), - "scunet_models_path": OptionInfo(os.path.join(paths.models_path, 'ScuNET'), "Path to directory with ScuNET model file(s)"), - "swinir_models_path": OptionInfo(os.path.join(paths.models_path, 'SwinIR'), "Path to directory with SwinIR model file(s)"), - "ldsr_models_path": OptionInfo(os.path.join(paths.models_path, 'LDSR'), "Path to directory with LDSR model file(s)"), - "clip_models_path": OptionInfo(os.path.join(paths.models_path, 'CLIP'), "Path to directory with CLIP model file(s)"), -})) - -options_templates.update(options_section(('saving-images', "Image Options"), { - "samples_save": OptionInfo(True, "Always save all generated images"), - "samples_format": OptionInfo('jpg', 'File format for generated images', gr.Dropdown, lambda: {"choices": ["jpg", "png", "webp", "tiff", "jp2"]}), - "samples_filename_pattern": OptionInfo("[seed]-[prompt_spaces]", "Images filename pattern", component_args=hide_dirs), - "save_images_add_number": OptionInfo(True, "Add number to filename when saving", component_args=hide_dirs), - "grid_save": OptionInfo(True, "Always save all generated image grids"), - "grid_format": OptionInfo('jpg', 'File format for grids', gr.Dropdown, lambda: {"choices": ["jpg", "png", "webp", "tiff", "jp2"]}), - "grid_extended_filename": OptionInfo(True, "Add extended info (seed, prompt) to filename when saving grid"), - "grid_only_if_multiple": OptionInfo(True, "Do not save grids consisting of one picture"), - "grid_prevent_empty_spots": OptionInfo(True, "Prevent empty spots in grid (when set to autodetect)"), - "n_rows": OptionInfo(-1, "Grid row count; use -1 for autodetect and 0 for it to be same as batch size", gr.Slider, {"minimum": -1, "maximum": 16, "step": 1}), - "save_txt": OptionInfo(False, "Create a text file next to every image with generation parameters"), - "save_log_fn": OptionInfo("", "Create a JSON log file with image information for each saved image", component_args=hide_dirs), - "save_images_before_face_restoration": OptionInfo(False, "Save a copy of image before doing face restoration"), - "save_images_before_highres_fix": OptionInfo(False, "Save a copy of image before applying highres fix"), - "save_images_before_color_correction": OptionInfo(False, "Save a copy of image before applying color correction to img2img results"), - "save_mask": OptionInfo(False, "Save a copy of the inpainting greyscale mask"), - "save_mask_composite": OptionInfo(False, "Save a copy of inpainting masked composite"), - "save_init_img": OptionInfo(False, "Save a copy of processing init images"), - "jpeg_quality": OptionInfo(85, "Quality for saved jpeg images", gr.Slider, {"minimum": 1, "maximum": 100, "step": 1}), - "webp_lossless": OptionInfo(False, "Use lossless compression for webp images"), - "img_max_size_mp": OptionInfo(250, "Maximum allowed image size in megapixels", gr.Number), - "use_original_name_batch": OptionInfo(True, "Use original name for output filename during batch process in extras tab"), - "use_upscaler_name_as_suffix": OptionInfo(True, "Use upscaler name as filename suffix in the extras tab"), - "save_selected_only": OptionInfo(True, "When using 'Save' button, only save a single selected image"), - "save_to_dirs": OptionInfo(False, "Save images to a subdirectory"), - "grid_save_to_dirs": OptionInfo(False, "Save grids to a subdirectory"), - "use_save_to_dirs_for_ui": OptionInfo(False, "Save images to a subdirectory when using Save button"), - "directories_filename_pattern": OptionInfo("[date]", "Directory name pattern", component_args=hide_dirs), - "directories_max_prompt_words": OptionInfo(8, "Max prompt words for [prompt_words] pattern", gr.Slider, {"minimum": 1, "maximum": 20, "step": 1, **hide_dirs}), -})) - -options_templates.update(options_section(('image-processing', "Image Processing"), { - "img2img_color_correction": OptionInfo(False, "Apply color correction to match original colors"), - "img2img_fix_steps": OptionInfo(False, "For image processing do exact number of steps as specified"), - "img2img_background_color": OptionInfo("#ffffff", "Image transparent color fill", ui_components.FormColorPicker, {}), - "inpainting_mask_weight": OptionInfo(1.0, "Inpainting conditioning mask strength", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), - "initial_noise_multiplier": OptionInfo(1.0, "Noise multiplier for image processing", gr.Slider, {"minimum": 0.1, "maximum": 1.5, "step": 0.01}), - "CLIP_stop_at_last_layers": OptionInfo(1, "Clip skip", gr.Slider, {"minimum": 1, "maximum": 8, "step": 1, "visible": False}), -})) - - -options_templates.update(options_section(('saving-paths', "Output Paths"), { - "outdir_samples": OptionInfo("", "Output directory for images; if empty, defaults to three directories below", component_args=hide_dirs), - "outdir_txt2img_samples": OptionInfo("outputs/text", 'Output directory for txt2img images', component_args=hide_dirs), - "outdir_img2img_samples": OptionInfo("outputs/image", 'Output directory for img2img images', component_args=hide_dirs), - "outdir_extras_samples": OptionInfo("outputs/extras", 'Output directory for images from extras tab', component_args=hide_dirs), - "outdir_grids": OptionInfo("", "Output directory for grids; if empty, defaults to two directories below", component_args=hide_dirs), - "outdir_txt2img_grids": OptionInfo("outputs/grids", 'Output directory for txt2img grids', component_args=hide_dirs), - "outdir_img2img_grids": OptionInfo("outputs/grids", 'Output directory for img2img grids', component_args=hide_dirs), - "outdir_save": OptionInfo("outputs/save", "Directory for saving images using the Save button", component_args=hide_dirs), - "outdir_init_images": OptionInfo("outputs/init-images", "Directory for saving init images when using img2img", component_args=hide_dirs), -})) - -options_templates.update(options_section(('ui', "User interface"), { - "gradio_theme": OptionInfo("black-orange", "UI theme", gr.Dropdown, lambda: {"choices": list_themes()}, refresh=refresh_themes), - "theme_style": OptionInfo("Auto", "Theme mode", gr.Radio, {"choices": ["Auto", "Dark", "Light"]}), - "return_grid": OptionInfo(True, "Show grid in results for web"), - "return_mask": OptionInfo(False, "For inpainting, include the greyscale mask in results for web"), - "return_mask_composite": OptionInfo(False, "For inpainting, include masked composite in results for web"), - "disable_weights_auto_swap": OptionInfo(True, "Do not change the selected model when reading generation parameters"), - "send_seed": OptionInfo(True, "Send seed when sending prompt or image to other interface"), - "send_size": OptionInfo(True, "Send size when sending prompt or image to another interface"), - "font": OptionInfo("", "Font for image grids that have text"), - "keyedit_precision_attention": OptionInfo(0.1, "Ctrl+up/down precision when editing (attention:1.1)", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}), - "keyedit_precision_extra": OptionInfo(0.05, "Ctrl+up/down precision when editing ", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}), - "keyedit_delimiters": OptionInfo(".,\/!?%^*;:{}=`~()", "Ctrl+up/down word delimiters"), # pylint: disable=anomalous-backslash-in-string - "quicksettings_list": OptionInfo(["sd_model_checkpoint"], "Quicksettings list", ui_components.DropdownMulti, lambda: {"choices": list(opts.data_labels.keys())}), - "hidden_tabs": OptionInfo([], "Hidden UI tabs", ui_components.DropdownMulti, lambda: {"choices": [x for x in tab_names]}), - "ui_tab_reorder": OptionInfo("From Text, From Image, Process Image", "UI tabs order"), - "ui_scripts_reorder": OptionInfo("Enable Dynamic Thresholding, ControlNet", "UI scripts order"), - "ui_reorder": OptionInfo(", ".join(ui_reorder_categories), "txt2img/img2img UI item order"), - "ui_extra_networks_tab_reorder": OptionInfo("Checkpoints, Lora, LyCORIS, Textual Inversion, Hypernetworks", "Extra networks tab order"), -})) - -options_templates.update(options_section(('live-preview', "Live previews"), { - "show_progressbar": OptionInfo(True, "Show progressbar"), - "live_previews_enable": OptionInfo(True, "Show live previews of the created image"), - "show_progress_grid": OptionInfo(True, "Show previews of all images generated in a batch as a grid"), - "notification_audio_enable": OptionInfo(False, "Play a sound when images are finished generating"), - "notification_audio_path": OptionInfo("html/notification.mp3","Path to notification sound", component_args=hide_dirs), - "show_progress_every_n_steps": OptionInfo(1, "Live preview display period", gr.Slider, {"minimum": -1, "maximum": 32, "step": 1}), - "show_progress_type": OptionInfo("TAESD", "Live preview method", gr.Radio, {"choices": ["Full VAE", "Approximate NN", "Approximate simple", "TAESD"]}), - "live_preview_content": OptionInfo("Combined", "Live preview subject", gr.Radio, {"choices": ["Combined", "Prompt", "Negative prompt"]}), - "live_preview_refresh_period": OptionInfo(250, "Progressbar/preview update period, in milliseconds") -})) - -options_templates.update(options_section(('sampler-params', "Sampler Settings"), { - "show_samplers": OptionInfo(["Euler a", "UniPC", "DDIM", "DPM++ 2M SDE", "DPM++ 2M SDE Karras", "DPM2 Karras", "DPM++ 2M Karras"], "Show samplers in user interface", gr.CheckboxGroup, lambda: {"choices": [x.name for x in list_samplers() if x.name != "PLMS"]}), - "fallback_sampler": OptionInfo("Euler a", "Secondary sampler", gr.Dropdown, lambda: {"choices": ["None"] + [x.name for x in list_samplers()]}), - "force_latent_sampler": OptionInfo("None", "Force latent upscaler sampler", gr.Dropdown, lambda: {"choices": ["None"] + [x.name for x in list_samplers()]}), - "eta_ancestral": OptionInfo(1.0, "Noise multiplier for ancestral samplers (eta)", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), - "eta_ddim": OptionInfo(0.0, "Noise multiplier for DDIM (eta)", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), - "ddim_discretize": OptionInfo('uniform', "DDIM discretize img2img", gr.Radio, {"choices": ['uniform', 'quad']}), - 's_churn': OptionInfo(0.0, "sigma churn", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), - 's_min_uncond': OptionInfo(0, "Negative Guidance minimum sigma", gr.Slider, {"minimum": 0.0, "maximum": 4.0, "step": 0.01}), - 's_tmin': OptionInfo(0.0, "sigma tmin", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), - 's_noise': OptionInfo(1.0, "sigma noise", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), - 'eta_noise_seed_delta': OptionInfo(0, "Noise seed delta (eta)", gr.Number, {"precision": 0}), - 'always_discard_next_to_last_sigma': OptionInfo(False, "Always discard next-to-last sigma"), - 'uni_pc_variant': OptionInfo("bh1", "UniPC variant", gr.Radio, {"choices": ["bh1", "bh2", "vary_coeff"]}), - 'uni_pc_skip_type': OptionInfo("time_uniform", "UniPC skip type", gr.Radio, {"choices": ["time_uniform", "time_quadratic", "logSNR"]}), - 'uni_pc_order': OptionInfo(3, "UniPC order (must be < sampling steps)", gr.Slider, {"minimum": 1, "maximum": 50, "step": 1}), - 'uni_pc_lower_order_final': OptionInfo(True, "UniPC lower order final"), -})) - -options_templates.update(options_section(('postprocessing', "Postprocessing"), { - 'postprocessing_enable_in_main_ui': OptionInfo([], "Enable addtional postprocessing operations", ui_components.DropdownMulti, lambda: {"choices": [x.name for x in shared_items.postprocessing_scripts()]}), - 'postprocessing_operation_order': OptionInfo([], "Postprocessing operation order", ui_components.DropdownMulti, lambda: {"choices": [x.name for x in shared_items.postprocessing_scripts()]}), - 'upscaling_max_images_in_cache': OptionInfo(5, "Maximum number of images in upscaling cache", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), -})) - -options_templates.update(options_section(('training', "Training"), { - "unload_models_when_training": OptionInfo(False, "Move VAE and CLIP to RAM when training if possible"), - "pin_memory": OptionInfo(True, "Pin training dataset to memory"), - "save_optimizer_state": OptionInfo(False, "Saves resumable optimizer state when training embedding or hypernetwork"), - "save_training_settings_to_txt": OptionInfo(True, "Save textual inversion and hypernet settings to a text file whenever training starts"), - "dataset_filename_word_regex": OptionInfo("", "Filename word regex"), - "dataset_filename_join_string": OptionInfo(" ", "Filename join string"), - "embeddings_templates_dir": OptionInfo(os.path.join(paths.script_path, 'train', 'templates'), "Embeddings train templates directory"), - "training_image_repeats_per_epoch": OptionInfo(1, "Number of repeats for a single input image per epoch; used only for displaying epoch number", gr.Number, {"precision": 0}), - "training_write_csv_every": OptionInfo(0, "Save an csv containing the loss to log directory every N steps, 0 to disable"), - "training_enable_tensorboard": OptionInfo(False, "Enable tensorboard logging"), - "training_tensorboard_save_images": OptionInfo(False, "Save generated images within tensorboard"), - "training_tensorboard_flush_every": OptionInfo(120, "How often, in seconds, to flush the pending tensorboard events and summaries to disk"), -})) - -options_templates.update(options_section(('interrogate', "Interrogate"), { - "interrogate_keep_models_in_memory": OptionInfo(False, "Interrogate: keep models in VRAM"), - "interrogate_return_ranks": OptionInfo(True, "Interrogate: include ranks of model tags matches in results"), - "interrogate_clip_num_beams": OptionInfo(1, "Interrogate: num_beams for BLIP", gr.Slider, {"minimum": 1, "maximum": 16, "step": 1}), - "interrogate_clip_min_length": OptionInfo(32, "Interrogate: minimum description length (excluding artists, etc..)", gr.Slider, {"minimum": 1, "maximum": 128, "step": 1}), - "interrogate_clip_max_length": OptionInfo(192, "Interrogate: maximum description length", gr.Slider, {"minimum": 1, "maximum": 256, "step": 1}), - "interrogate_clip_dict_limit": OptionInfo(2048, "CLIP: maximum number of lines in text file (0 = No limit)"), - "interrogate_clip_skip_categories": OptionInfo(["artists", "movements", "flavors"], "CLIP: skip inquire categories", gr.CheckboxGroup, lambda: {"choices": modules.interrogate.category_types()}, refresh=modules.interrogate.category_types), - "interrogate_deepbooru_score_threshold": OptionInfo(0.65, "Interrogate: deepbooru score threshold", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01}), - "deepbooru_sort_alpha": OptionInfo(False, "Interrogate: deepbooru sort alphabetically"), - "deepbooru_use_spaces": OptionInfo(False, "use spaces for tags in deepbooru"), - "deepbooru_escape": OptionInfo(True, "escape (\\) brackets in deepbooru (so they are used as literal brackets and not for emphasis)"), - "deepbooru_filter_tags": OptionInfo("", "filter out those tags from deepbooru output (separated by comma)"), -})) - -options_templates.update(options_section(('upscaling', "Upscaling"), { - "upscaler_for_img2img": OptionInfo("None", "Default upscaler for image resize operations", gr.Dropdown, lambda: {"choices": [x.name for x in sd_upscalers]}), - "realesrgan_enabled_models": OptionInfo(["R-ESRGAN 4x+", "R-ESRGAN 4x+ Anime6B"], "Real-ESRGAN available models", gr.CheckboxGroup, lambda: {"choices": shared_items.realesrgan_models_names()}), - "ESRGAN_tile": OptionInfo(192, "Tile size for ESRGAN upscalers (0 = no tiling)", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}), - "ESRGAN_tile_overlap": OptionInfo(8, "Tile overlap in pixels for ESRGAN upscalers", gr.Slider, {"minimum": 0, "maximum": 48, "step": 1}), - "SCUNET_tile": OptionInfo(256, "Tile size for SCUNET upscalers (0 = no tiling)", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}), - "SCUNET_tile_overlap": OptionInfo(8, "Tile overlap, in pixels for SCUNET upscalers (low values = visible seam)", gr.Slider, {"minimum": 0, "maximum": 64, "step": 1}), - "use_old_hires_fix_width_height": OptionInfo(False, "Hires fix uses width & height to set final resolution rather than first pass"), - "dont_fix_second_order_samplers_schedule": OptionInfo(False, "Do not fix prompt schedule for second order samplers"), -})) - -options_templates.update(options_section(('lora', "Lora"), { - "lyco_patch_lora": OptionInfo(False, "Use LyCoris handler for all Lora types", gr.Checkbox, { "visible": True }), - "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, { "visible": True }), -})) - -options_templates.update(options_section(('face-restoration', "Face restoration"), { - "face_restoration_model": OptionInfo("CodeFormer", "Face restoration model", gr.Radio, lambda: {"choices": [x.name() for x in face_restorers]}), - "code_former_weight": OptionInfo(0.2, "CodeFormer weight parameter; 0 = maximum effect; 1 = minimum effect", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01}), - "face_restoration_unload": OptionInfo(False, "Move face restoration model from VRAM into RAM after processing"), -})) - -options_templates.update(options_section(('extra_networks', "Extra Networks"), { - "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"), - "sd_hypernetwork": OptionInfo("None", "Add hypernetwork to prompt", gr.Dropdown, lambda: {"choices": ["None"] + [x for x in hypernetworks.keys()]}, refresh=reload_hypernetworks), -})) - -options_templates.update(options_section(('token_merging', 'Token Merging'), { - "token_merging": OptionInfo(False, "Enable redundant token merging via tomesd for speed and memory improvements", gr.Checkbox), - "token_merging_ratio": OptionInfo(0.5, "Token merging Ratio. Higher merging ratio = faster generation, smaller VRAM usage, lower quality", gr.Slider, {"minimum": 0, "maximum": 0.9, "step": 0.1}), - "token_merging_hr_only": OptionInfo(True, "Apply only to high-res fix pass. Disabling can yield a ~20-35% speedup on contemporary resolutions", gr.Checkbox), - "token_merging_ratio_hr": OptionInfo(0.5, "Merging Ratio for high-res pass", gr.Slider, {"minimum": 0, "maximum": 0.9, "step": 0.1}), - "token_merging_random": OptionInfo(False, "Use random perturbations - Can improve outputs for certain samplers. For others, it may cause visual artifacting", gr.Checkbox), - "token_merging_merge_attention": OptionInfo(True, "Merge attention (Recommend on)", gr.Checkbox), - "token_merging_merge_cross_attention": OptionInfo(False, "Merge cross attention (Recommend off)", gr.Checkbox), - "token_merging_merge_mlp": OptionInfo(False, "Merge mlp (Strongly recommend off)", gr.Checkbox), - "token_merging_maximum_down_sampling": OptionInfo(1, "Maximum down sampling", gr.Radio, lambda: {"choices": [1, 2, 4, 8]}), - "token_merging_stride_x": OptionInfo(2, "Stride - X", gr.Slider, {"minimum": 2, "maximum": 8, "step": 2}), - "token_merging_stride_y": OptionInfo(2, "Stride - Y", gr.Slider, {"minimum": 2, "maximum": 8, "step": 2}) -})) - -options_templates.update(options_section((None, "Hidden options"), { - "disabled_extensions": OptionInfo([], "Disable these extensions"), - "disable_all_extensions": OptionInfo("none", "Disable all extensions (preserves the list of disabled extensions)", gr.Radio, {"choices": ["none", "user", "all"]}), - "sd_checkpoint_hash": OptionInfo("", "SHA256 hash of the current checkpoint"), -})) - -options_templates.update() - - -class Options: - data = None - data_labels = options_templates - typemap = {int: float} - - def __init__(self): - self.data = {k: v.default for k, v in self.data_labels.items()} - - def __setattr__(self, key, value): - if self.data is not None: - if key in self.data or key in self.data_labels: - if cmd_opts.freeze: - log.warning(f'Settings are frozen: {key}') - return - if cmd_opts.hide_ui_dir_config and key in restricted_opts: - log.warning(f'Settings key is restricted: {key}') - return - else: - self.data[key] = value - return - - return super(Options, self).__setattr__(key, value) - - def __getattr__(self, item): - if self.data is not None: - if item in self.data: - return self.data[item] - if item in self.data_labels: - return self.data_labels[item].default - return super(Options, self).__getattribute__(item) - - def set(self, key, value): - """sets an option and calls its onchange callback, returning True if the option changed and False otherwise""" - oldval = self.data.get(key, None) - if oldval == value: - return False - try: - setattr(self, key, value) - except RuntimeError: - return False - if self.data_labels[key].onchange is not None: - try: - self.data_labels[key].onchange() - except Exception as e: - errors.display(e, f"changing setting {key} to {value}") - setattr(self, key, oldval) - return False - return True - - def get_default(self, key): - """returns the default value for the key""" - data_label = self.data_labels.get(key) - if data_label is None: - return None - return data_label.default - - def save(self, filename): - if cmd_opts.freeze: - log.warning(f'Settings saving is disabled: {filename}') - return - with open(filename, "w", encoding="utf8") as file: - json.dump(self.data, file, indent=4) - - def same_type(self, x, y): - if x is None or y is None: - return True - type_x = self.typemap.get(type(x), type(x)) - type_y = self.typemap.get(type(y), type(y)) - return type_x == type_y - - def load(self, filename): - if not os.path.isfile(filename): - log.debug(f'Created default config: {filename}') - self.save(filename) - return - with open(filename, "r", encoding="utf8") as file: - self.data = json.load(file) - if self.data.get('quicksettings') is not None and self.data.get('quicksettings_list') is None: - self.data['quicksettings_list'] = [i.strip() for i in self.data.get('quicksettings').split(',')] - bad_settings = 0 - for k, v in self.data.items(): - info = self.data_labels.get(k, None) - if info is not None and not self.same_type(info.default, v): - log.error(f"Warning: bad setting value: {k}: {v} ({type(v).__name__}; expected {type(info.default).__name__})") - bad_settings += 1 - if bad_settings > 0: - log.error(f"Error: Bad settings found in {filename}") - - def onchange(self, key, func, call=True): - item = self.data_labels.get(key) - item.onchange = func - if call: - func() - - def dumpjson(self): - d = {k: self.data.get(k, self.data_labels.get(k).default) for k in self.data_labels.keys()} - metadata = { - k: { - "is_stored": k in self.data, - "tab_name": v.section[0] - } for k, v in self.data_labels.items() - } - return json.dumps({"values": d, "metadata": metadata}) - - def add_option(self, key, info): - self.data_labels[key] = info - - def reorder(self): - """reorder settings so that all items related to section always go together""" - section_ids = {} - settings_items = self.data_labels.items() - for k, item in settings_items: - if item.section not in section_ids: - section_ids[item.section] = len(section_ids) - self.data_labels = {k: v for k, v in sorted(settings_items, key=lambda x: section_ids[x[1].section])} - - def cast_value(self, key, value): - """casts an arbitrary to the same type as this setting's value with key - Example: cast_value("eta_noise_seed_delta", "12") -> returns 12 (an int rather than str) - """ - if value is None: - return None - default_value = self.data_labels[key].default - if default_value is None: - default_value = getattr(self, key, None) - if default_value is None: - return None - expected_type = type(default_value) - if expected_type == bool and value == "False": - value = False - elif expected_type == type(value): - pass - else: - value = expected_type(value) - return value - - -opts = Options() -config_filename = cmd_opts.config -opts.load(config_filename) -cmd_opts = cmd_args.compatibility_args(opts, cmd_opts) -if cmd_opts.backend == 'diffusers': - log.info('Overriding backend to Diffusers') - opts.data['sd_backend'] = 'Diffusers' -if cmd_opts.backend == 'original': - log.info('Overriding backend to Diffusers') - opts.data['sd_backend'] = 'Original' -backend = Backend.DIFFUSERS if opts.sd_backend == 'Diffusers' else Backend.ORIGINAL - -prompt_styles = modules.styles.StyleDatabase(opts.styles_dir) -cmd_opts.disable_extension_access = (cmd_opts.share or cmd_opts.listen or (cmd_opts.server_name or False)) and not cmd_opts.insecure -devices.device, devices.device_interrogate, devices.device_gfpgan, devices.device_esrgan, devices.device_codeformer = (devices.cpu if any(y in cmd_opts.use_cpu for y in [x, 'all']) else devices.get_optimal_device() for x in ['sd', 'interrogate', 'gfpgan', 'esrgan', 'codeformer']) -device = devices.device -batch_cond_uncond = opts.always_batch_cond_uncond or not (cmd_opts.lowvram or cmd_opts.medvram) -parallel_processing_allowed = not cmd_opts.lowvram and not cmd_opts.medvram -mem_mon = modules.memmon.MemUsageMonitor("MemMon", device, opts) -mem_mon.start() -if device.type == 'privateuseone': - import modules.dml # pylint: disable=ungrouped-imports - - -def reload_gradio_theme(theme_name=None): - global gradio_theme # pylint: disable=global-statement - if not theme_name: - theme_name = opts.gradio_theme - default_font_params = {} - res = 0 - try: - req = urllib.request.Request("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono", method="HEAD") - res = urllib.request.urlopen(req, timeout=3.0).status - except: - res = 0 - if res != 200: - log.info('No internet access detected, using default fonts') - default_font_params = { - 'font':['Helvetica', 'ui-sans-serif', 'system-ui', 'sans-serif'], - 'font_mono':['IBM Plex Mono', 'ui-monospace', 'Consolas', 'monospace'] - } - if theme_name == "black-orange": - gradio_theme = gr.themes.Default(**default_font_params) - elif theme_name.startswith("gradio/"): - if theme_name == "gradio/default": - gradio_theme = gr.themes.Default(**default_font_params) - if theme_name == "gradio/base": - gradio_theme = gr.themes.Base(**default_font_params) - if theme_name == "gradio/glass": - gradio_theme = gr.themes.Glass(**default_font_params) - if theme_name == "gradio/monochrome": - gradio_theme = gr.themes.Monochrome(**default_font_params) - if theme_name == "gradio/soft": - gradio_theme = gr.themes.Soft(**default_font_params) - else: - try: - gradio_theme = gr.themes.ThemeClass.from_hub(theme_name) - except: - log.error("Theme download error accessing HuggingFace") - gradio_theme = gr.themes.Default(**default_font_params) - log.info(f'Loading UI theme: name={theme_name} style={opts.theme_style}') - - -class TotalTQDM: - def __init__(self): - self._tqdm = None - - def reset(self): - self._tqdm = tqdm.tqdm( - desc="Total", - total=state.job_count * state.sampling_steps, - position=1, - ) - - def update(self): - if not opts.multiple_tqdm or cmd_opts.disable_console_progressbars: - return - if self._tqdm is None: - self.reset() - self._tqdm.update() - - def updateTotal(self, new_total): - if not opts.multiple_tqdm or cmd_opts.disable_console_progressbars: - return - if self._tqdm is None: - self.reset() - self._tqdm.total = new_total - - def clear(self): - if self._tqdm is not None: - self._tqdm.refresh() - self._tqdm.close() - self._tqdm = None - -total_tqdm = TotalTQDM() - - -def restart_server(restart=True): - if demo is None: - return - log.info('Server shutdown requested') - try: - demo.server.wants_restart = restart - demo.server.should_exit = True - demo.server.force_exit = True - demo.close(verbose=False) - demo.server.close() - demo.fns = [] - except: - pass - if restart: - log.info('Server will restart') - - -def restore_defaults(restart=True): - if os.path.exists(cmd_opts.config): - log.info('Restoring server defaults') - os.remove(cmd_opts.config) - if os.path.exists(cmd_opts.ui_config): - log.info('Restoring UI defaults') - os.remove(cmd_opts.ui_config) - restart_server(restart) - - -def listfiles(dirname): - filenames = [os.path.join(dirname, x) for x in sorted(os.listdir(dirname), key=str.lower) if not x.startswith(".")] - return [file for file in filenames if os.path.isfile(file)] - - -def walk_files(path, allowed_extensions=None): - if not os.path.exists(path): - return - if allowed_extensions is not None: - allowed_extensions = set(allowed_extensions) - for root, _dirs, files in os.walk(path, followlinks=True): - for filename in files: - if allowed_extensions is not None: - _, ext = os.path.splitext(filename) - if ext not in allowed_extensions: - continue - yield os.path.join(root, filename) - - -def html_path(filename): - return os.path.join(paths.script_path, "html", filename) - - -def html(filename): - path = html_path(filename) - if os.path.exists(path): - with open(path, encoding="utf8") as file: - return file.read() - return "" - - -def get_version(): - version = None - if version is None: - try: - import subprocess - res = subprocess.run('git log --pretty=format:"%h %ad" -1 --date=short', stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell=True, check=True) - ver = res.stdout.decode(encoding = 'utf8', errors='ignore') if len(res.stdout) > 0 else ' ' - githash, updated = ver.split(' ') - res = subprocess.run('git remote get-url origin', stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell=True, check=True) - origin = res.stdout.decode(encoding = 'utf8', errors='ignore') if len(res.stdout) > 0 else '' - res = subprocess.run('git branch --show-current', stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell=True, check=True) - branch = res.stdout.decode(encoding = 'utf8', errors='ignore') if len(res.stdout) > 0 else '' - version = { - 'app': 'sd.next', - 'updated': updated, - 'hash': githash, - 'url': origin.replace('\n', '') + '/tree/' + branch.replace('\n', '') - } - except: - version = { 'app': 'sd.next' } - return version - - -class Shared(sys.modules[__name__].__class__): - # this class is here to provide sd_model field as a property, so that it can be created and loaded on demand rather than at program startup. - sd_model_val = None - - @property - def sd_model(self): - import modules.sd_models # pylint: disable=W0621 - # return modules.sd_models.model_data.sd_model - return modules.sd_models.model_data.get_sd_model() - - @sd_model.setter - def sd_model(self, value): - import modules.sd_models # pylint: disable=W0621 - modules.sd_models.model_data.set_sd_model(value) - -# sd_model: LatentDiffusion = None # this var is here just for IDE's type checking; it cannot be accessed because the class field above will be accessed instead -sd_model = None -sys.modules[__name__].__class__ = Shared +import os +import sys +import time +import json +import datetime +import urllib.request +from enum import Enum +import gradio as gr +import tqdm +import requests +from modules import errors, ui_components, shared_items, cmd_args +from modules.paths_internal import models_path, script_path, data_path, sd_configs_path, sd_default_config, sd_model_file, default_sd_model_file, extensions_dir, extensions_builtin_dir # pylint: disable=W0611 +import modules.interrogate +import modules.memmon +import modules.styles +import modules.devices as devices +import modules.paths_internal as paths +from installer import log as central_logger # pylint: disable=E0611 + + +errors.install(gr) +demo: gr.Blocks = None +log = central_logger +progress_print_out = sys.stdout +parser = cmd_args.parser +url = 'https://github.com/vladmandic/automatic' +cmd_opts, _ = parser.parse_known_args() +hide_dirs = {"visible": not cmd_opts.hide_ui_dir_config} +xformers_available = False +clip_model = None +interrogator = modules.interrogate.InterrogateModels("interrogate") +sd_upscalers = [] +face_restorers = [] +tab_names = [] +options_templates = {} +hypernetworks = {} +loaded_hypernetworks = [] +gradio_theme = gr.themes.Base() +settings_components = None +latent_upscale_default_mode = "Latent" +latent_upscale_modes = { + "Latent": {"mode": "bilinear", "antialias": False}, + "Latent (antialiased)": {"mode": "bilinear", "antialias": True}, + "Latent (bicubic)": {"mode": "bicubic", "antialias": False}, + "Latent (bicubic antialiased)": {"mode": "bicubic", "antialias": True}, + "Latent (nearest)": {"mode": "nearest", "antialias": False}, + "Latent (nearest-exact)": {"mode": "nearest-exact", "antialias": False}, +} +restricted_opts = { + "samples_filename_pattern", + "directories_filename_pattern", + "outdir_samples", + "outdir_txt2img_samples", + "outdir_img2img_samples", + "outdir_extras_samples", + "outdir_grids", + "outdir_txt2img_grids", + "outdir_save", + "outdir_init_images" +} +ui_reorder_categories = [ + "inpaint", + "sampler", + "checkboxes", + "hires_fix", + "dimensions", + "cfg", + "seed", + "batch", + "override_settings", + "scripts", +] + + +class Backend(Enum): + ORIGINAL = 1 + DIFFUSERS = 2 + + +def reload_hypernetworks(): + from modules.hypernetworks import hypernetwork + global hypernetworks # pylint: disable=W0603 + hypernetworks = hypernetwork.list_hypernetworks(opts.hypernetwork_dir) + + +class State: + skipped = False + interrupted = False + paused = False + job = "" + job_no = 0 + job_count = 0 + processing_has_refined_job_count = False + job_timestamp = '0' + sampling_step = 0 + sampling_steps = 0 + current_latent = None + current_image = None + current_image_sampling_step = 0 + id_live_preview = 0 + textinfo = None + time_start = None + need_restart = False + server_start = None + oom = False + + def skip(self): + log.debug('Requested skip') + self.skipped = True + + def interrupt(self): + log.debug('Requested interrupt') + self.interrupted = True + + def pause(self): + self.paused = not self.paused + log.debug(f'Requested {"pause" if self.paused else "continue"}') + + def nextjob(self): + if opts.live_previews_enable and opts.show_progress_every_n_steps == -1: + self.do_set_current_image() + self.job_no += 1 + self.sampling_step = 0 + self.current_image_sampling_step = 0 + + def dict(self): + obj = { + "skipped": self.skipped, + "interrupted": self.interrupted, + "job": self.job, + "job_count": self.job_count, + "job_timestamp": self.job_timestamp, + "job_no": self.job_no, + "sampling_step": self.sampling_step, + "sampling_steps": self.sampling_steps, + } + return obj + + def begin(self): + self.sampling_step = 0 + self.job_count = -1 + self.processing_has_refined_job_count = False + self.job_no = 0 + self.job_timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S") + self.current_latent = None + self.current_image = None + self.current_image_sampling_step = 0 + self.id_live_preview = 0 + self.skipped = False + self.interrupted = False + self.paused = False + self.textinfo = None + self.time_start = time.time() + devices.torch_gc() + + def end(self): + self.job = "" + self.job_count = 0 + self.paused = False + devices.torch_gc() + + def set_current_image(self): + """sets self.current_image from self.current_latent if enough sampling steps have been made after the last call to this""" + if not parallel_processing_allowed: + return + if self.sampling_step - self.current_image_sampling_step >= opts.show_progress_every_n_steps and opts.live_previews_enable and opts.show_progress_every_n_steps != -1: + self.do_set_current_image() + + def do_set_current_image(self): + if self.current_latent is None: + return + import modules.sd_samplers # pylint: disable=W0621 + if opts.show_progress_grid: + self.assign_current_image(modules.sd_samplers.samples_to_image_grid(self.current_latent)) + else: + self.assign_current_image(modules.sd_samplers.sample_to_image(self.current_latent)) + self.current_image_sampling_step = self.sampling_step + + def assign_current_image(self, image): + self.current_image = image + self.id_live_preview += 1 + +state = State() +state.server_start = time.time() + + +class OptionInfo: + def __init__(self, default=None, label="", component=None, component_args=None, onchange=None, section=None, refresh=None, comment_before='', comment_after=''): + self.default = default + self.label = label + self.component = component + self.component_args = component_args + self.onchange = onchange + self.section = section + self.refresh = refresh + self.comment_before = comment_before # HTML text that will be added after label in UI + self.comment_after = comment_after # HTML text that will be added before label in UI + + def link(self, label, uri): + self.comment_before += f"[{label}]" + return self + + def js(self, label, js_func): + self.comment_before += f"[{label}]" + return self + + def info(self, info): + self.comment_after += f"({info})" + return self + + def needs_restart(self): + self.comment_after += " (requires restart)" + return self + + +def options_section(section_identifier, options_dict): + for v in options_dict.values(): + v.section = section_identifier + return options_dict + + +def list_checkpoint_tiles(): + import modules.sd_models # pylint: disable=W0621 + return modules.sd_models.checkpoint_tiles() + + +default_checkpoint = list_checkpoint_tiles()[0] if len(list_checkpoint_tiles()) > 0 else "model.ckpt" + + +def refresh_checkpoints(): + import modules.sd_models # pylint: disable=W0621 + return modules.sd_models.list_models() + + +def list_samplers(): + import modules.sd_samplers # pylint: disable=W0621 + modules.sd_samplers.set_samplers() + return modules.sd_samplers.all_samplers + +def list_themes(): + fn = os.path.join('html', 'themes.json') + if not os.path.exists(fn): + refresh_themes() + if os.path.exists(fn): + with open(fn, mode='r', encoding='utf=8') as f: + res = json.loads(f.read()) + else: + res = [] + builtin = ["black-orange", "gradio/default", "gradio/base", "gradio/glass", "gradio/monochrome", "gradio/soft"] + themes = sorted(set(builtin + [x['id'] for x in res if x['status'] == 'RUNNING' and 'test' not in x['id'].lower()]), key=str.casefold) + return themes + + +def lora_disable(): + if opts.lora_disable: + if 'Lora' not in opts.disabled_extensions: + opts.data['disabled_extensions'].append('Lora') + else: + opts.data['disabled_extensions'] = [x for x in opts.disabled_extensions if x != 'Lora'] + + +def refresh_themes(): + try: + req = requests.get('https://huggingface.co/datasets/freddyaboulton/gradio-theme-subdomains/resolve/main/subdomains.json', timeout=5) + if req.status_code == 200: + res = req.json() + fn = os.path.join('html', 'themes.json') + with open(fn, mode='w', encoding='utf=8') as f: + f.write(json.dumps(res)) + else: + log.error('Error refreshing UI themes') + except: + log.error('Exception refreshing UI themes') + + +if devices.backend == "cpu": + cross_attention_optimization_default = "Doggettx's" +elif devices.backend == "mps": + cross_attention_optimization_default = "Doggettx's" +elif devices.backend == "ipex": + cross_attention_optimization_default = "InvokeAI's" +elif devices.backend == "directml": + cross_attention_optimization_default = "Sub-quadratic" +elif devices.backend == "rocm": + cross_attention_optimization_default = "Sub-quadratic" +else: # cuda + cross_attention_optimization_default ="Scaled-Dot-Product" + +options_templates.update(options_section(('sd', "Stable Diffusion"), { + "sd_model_checkpoint": OptionInfo(default_checkpoint, "Stable Diffusion checkpoint", gr.Dropdown, lambda: {"choices": list_checkpoint_tiles()}, refresh=refresh_checkpoints), + "sd_checkpoint_cache": OptionInfo(0, "Number of cached model checkpoints", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), + "sd_vae_checkpoint_cache": OptionInfo(0, "Number of cached VAE checkpoints", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), + "sd_vae": OptionInfo("Automatic", "Select VAE", gr.Dropdown, lambda: {"choices": shared_items.sd_vae_items()}, refresh=shared_items.refresh_vae_list), + "sd_model_dict": OptionInfo('None', "Stable Diffusion checkpoint dict", gr.Dropdown, lambda: {"choices": ['None'] + list_checkpoint_tiles()}, refresh=refresh_checkpoints), + "sd_vae_sliced_encode": OptionInfo(False, "Enable splitting of hires batch processing"), + "stream_load": OptionInfo(False, "When loading models attempt stream loading optimized for slow or network storage"), + "model_reuse_dict": OptionInfo(False, "When loading models attempt to reuse previous model dictionary"), + "cross_attention_optimization": OptionInfo(cross_attention_optimization_default, "Cross-attention optimization method", gr.Radio, lambda: {"choices": shared_items.list_crossattention() }), + "cross_attention_options": OptionInfo([], "Cross-attention advanced options", gr.CheckboxGroup, lambda: {"choices": ['xFormers enable flash Attention', 'SDP disable memory attention']}), + "sub_quad_q_chunk_size": OptionInfo(512, "Sub-quadratic cross-attention query chunk size", gr.Slider, {"minimum": 16, "maximum": 8192, "step": 8}), + "sub_quad_kv_chunk_size": OptionInfo(512, "Sub-quadratic cross-attention kv chunk size", gr.Slider, {"minimum": 0, "maximum": 8192, "step": 8}), + "sub_quad_chunk_threshold": OptionInfo(80, "Sub-quadratic cross-attention chunking threshold", gr.Slider, {"minimum": 0, "maximum": 100, "step": 1}), + "prompt_attention": OptionInfo("Full parser", "Prompt attention parser", gr.Radio, lambda: {"choices": ["Full parser", "Compel parser", "A1111 parser", "Fixed attention"] }), + "prompt_mean_norm": OptionInfo(True, "Prompt attention mean normalization"), + "always_batch_cond_uncond": OptionInfo(False, "Disable conditional batching enabled on low memory systems"), + "enable_quantization": OptionInfo(True, "Enable samplers quantization for sharper and cleaner results"), + "comma_padding_backtrack": OptionInfo(20, "Increase coherency by padding from the last comma within n tokens when using more than 75 tokens", gr.Slider, {"minimum": 0, "maximum": 74, "step": 1 }), + "sd_backend": OptionInfo("Original", "Stable Diffusion backend (experimental)", gr.Radio, lambda: {"choices": ["Original", "Diffusers"] }), +})) + +options_templates.update(options_section(('cuda', "Compute Settings"), { + "memmon_poll_rate": OptionInfo(2, "VRAM usage polls per second during generation", gr.Slider, {"minimum": 0, "maximum": 40, "step": 1}), + "precision": OptionInfo("Autocast", "Precision type", gr.Radio, lambda: {"choices": ["Autocast", "Full"]}), + "cuda_dtype": OptionInfo("FP32" if sys.platform == "darwin" else "FP16", "Device precision type", gr.Radio, lambda: {"choices": ["FP32", "FP16", "BF16"]}), + "no_half": OptionInfo(False, "Use full precision for model (--no-half)", None, None, None), + "no_half_vae": OptionInfo(False, "Use full precision for VAE (--no-half-vae)"), + "upcast_sampling": OptionInfo(True if sys.platform == "darwin" else False, "Enable upcast sampling"), + "upcast_attn": OptionInfo(False, "Enable upcast cross attention layer"), + "disable_nan_check": OptionInfo(True, "Disable NaN check in produced images/latent spaces"), + "rollback_vae": OptionInfo(False, "Attempt to roll back VAE when produced NaN values, requires NaN check (experimental)"), + "opt_channelslast": OptionInfo(False, "Use channels last as torch memory format "), + "cudnn_benchmark": OptionInfo(False, "Enable full-depth cuDNN benchmark feature"), + "cuda_allow_tf32": OptionInfo(True, "Allow TF32 math ops"), + "cuda_allow_tf16_reduced": OptionInfo(True, "Allow TF16 reduced precision math ops"), + "cuda_compile": OptionInfo(False, "Enable model compile (experimental)"), + "cuda_compile_mode": OptionInfo("none", "Model compile mode (experimental)", gr.Radio, lambda: {"choices": ['none', 'inductor', 'cudagraphs', 'aot_ts_nvfuser', 'hidet', 'ipex']}), + "cuda_compile_verbose": OptionInfo(False, "Model compile verbose mode"), + "cuda_compile_errors": OptionInfo(True, "Model compile suppress errors"), + "disable_gc": OptionInfo(False, "Disable Torch memory garbage collection (experimental)"), +})) + +options_templates.update(options_section(('system-paths', "System Paths"), { + "temp_dir": OptionInfo("", "Directory for temporary images; leave empty for default"), + "clean_temp_dir_at_start": OptionInfo(True, "Cleanup non-default temporary directory when starting webui"), + "ckpt_dir": OptionInfo(os.path.join(paths.models_path, 'Stable-diffusion'), "Path to directory with stable diffusion checkpoints"), + "diffusers_dir": OptionInfo(os.path.join(paths.models_path, 'Diffusers'), "Path to directory with stable diffusion diffusers"), + "vae_dir": OptionInfo(os.path.join(paths.models_path, 'VAE'), "Path to directory with VAE files"), + "lora_dir": OptionInfo(os.path.join(paths.models_path, 'Lora'), "Path to directory with Lora network(s)"), + "lyco_dir": OptionInfo(os.path.join(paths.models_path, 'LyCORIS'), "Path to directory with LyCORIS network(s)"), + "styles_dir": OptionInfo(os.path.join(paths.data_path, 'styles.csv'), "Path to user-defined styles file"), + "embeddings_dir": OptionInfo(os.path.join(paths.models_path, 'embeddings'), "Embeddings directory for textual inversion"), + "hypernetwork_dir": OptionInfo(os.path.join(paths.models_path, 'hypernetworks'), "Hypernetwork directory"), + "codeformer_models_path": OptionInfo(os.path.join(paths.models_path, 'Codeformer'), "Path to directory with codeformer model file(s)"), + "gfpgan_models_path": OptionInfo(os.path.join(paths.models_path, 'GFPGAN'), "Path to directory with GFPGAN model file(s)"), + "esrgan_models_path": OptionInfo(os.path.join(paths.models_path, 'ESRGAN'), "Path to directory with ESRGAN model file(s)"), + "bsrgan_models_path": OptionInfo(os.path.join(paths.models_path, 'BSRGAN'), "Path to directory with BSRGAN model file(s)"), + "realesrgan_models_path": OptionInfo(os.path.join(paths.models_path, 'RealESRGAN'), "Path to directory with RealESRGAN model file(s)"), + "scunet_models_path": OptionInfo(os.path.join(paths.models_path, 'ScuNET'), "Path to directory with ScuNET model file(s)"), + "swinir_models_path": OptionInfo(os.path.join(paths.models_path, 'SwinIR'), "Path to directory with SwinIR model file(s)"), + "ldsr_models_path": OptionInfo(os.path.join(paths.models_path, 'LDSR'), "Path to directory with LDSR model file(s)"), + "clip_models_path": OptionInfo(os.path.join(paths.models_path, 'CLIP'), "Path to directory with CLIP model file(s)"), +})) + +options_templates.update(options_section(('saving-images', "Image Options"), { + "samples_save": OptionInfo(True, "Always save all generated images"), + "samples_format": OptionInfo('jpg', 'File format for generated images', gr.Dropdown, lambda: {"choices": ["jpg", "png", "webp", "tiff", "jp2"]}), + "samples_filename_pattern": OptionInfo("[seed]-[prompt_spaces]", "Images filename pattern", component_args=hide_dirs), + "save_images_add_number": OptionInfo(True, "Add number to filename when saving", component_args=hide_dirs), + "grid_save": OptionInfo(True, "Always save all generated image grids"), + "grid_format": OptionInfo('jpg', 'File format for grids', gr.Dropdown, lambda: {"choices": ["jpg", "png", "webp", "tiff", "jp2"]}), + "grid_extended_filename": OptionInfo(True, "Add extended info (seed, prompt) to filename when saving grid"), + "grid_only_if_multiple": OptionInfo(True, "Do not save grids consisting of one picture"), + "grid_prevent_empty_spots": OptionInfo(True, "Prevent empty spots in grid (when set to autodetect)"), + "n_rows": OptionInfo(-1, "Grid row count; use -1 for autodetect and 0 for it to be same as batch size", gr.Slider, {"minimum": -1, "maximum": 16, "step": 1}), + "save_txt": OptionInfo(False, "Create a text file next to every image with generation parameters"), + "save_log_fn": OptionInfo("", "Create a JSON log file with image information for each saved image", component_args=hide_dirs), + "save_images_before_face_restoration": OptionInfo(False, "Save a copy of image before doing face restoration"), + "save_images_before_highres_fix": OptionInfo(False, "Save a copy of image before applying highres fix"), + "save_images_before_color_correction": OptionInfo(False, "Save a copy of image before applying color correction to img2img results"), + "save_mask": OptionInfo(False, "Save a copy of the inpainting greyscale mask"), + "save_mask_composite": OptionInfo(False, "Save a copy of inpainting masked composite"), + "save_init_img": OptionInfo(False, "Save a copy of processing init images"), + "jpeg_quality": OptionInfo(85, "Quality for saved jpeg images", gr.Slider, {"minimum": 1, "maximum": 100, "step": 1}), + "webp_lossless": OptionInfo(False, "Use lossless compression for webp images"), + "img_max_size_mp": OptionInfo(250, "Maximum allowed image size in megapixels", gr.Number), + "use_original_name_batch": OptionInfo(True, "Use original name for output filename during batch process in extras tab"), + "use_upscaler_name_as_suffix": OptionInfo(True, "Use upscaler name as filename suffix in the extras tab"), + "save_selected_only": OptionInfo(True, "When using 'Save' button, only save a single selected image"), + "save_to_dirs": OptionInfo(False, "Save images to a subdirectory"), + "grid_save_to_dirs": OptionInfo(False, "Save grids to a subdirectory"), + "use_save_to_dirs_for_ui": OptionInfo(False, "Save images to a subdirectory when using Save button"), + "directories_filename_pattern": OptionInfo("[date]", "Directory name pattern", component_args=hide_dirs), + "directories_max_prompt_words": OptionInfo(8, "Max prompt words for [prompt_words] pattern", gr.Slider, {"minimum": 1, "maximum": 20, "step": 1, **hide_dirs}), +})) + +options_templates.update(options_section(('image-processing', "Image Processing"), { + "img2img_color_correction": OptionInfo(False, "Apply color correction to match original colors"), + "img2img_fix_steps": OptionInfo(False, "For image processing do exact number of steps as specified"), + "img2img_background_color": OptionInfo("#ffffff", "Image transparent color fill", ui_components.FormColorPicker, {}), + "inpainting_mask_weight": OptionInfo(1.0, "Inpainting conditioning mask strength", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), + "initial_noise_multiplier": OptionInfo(1.0, "Noise multiplier for image processing", gr.Slider, {"minimum": 0.1, "maximum": 1.5, "step": 0.01}), + "CLIP_stop_at_last_layers": OptionInfo(1, "Clip skip", gr.Slider, {"minimum": 1, "maximum": 8, "step": 1, "visible": False}), +})) + + +options_templates.update(options_section(('saving-paths', "Output Paths"), { + "outdir_samples": OptionInfo("", "Output directory for images; if empty, defaults to three directories below", component_args=hide_dirs), + "outdir_txt2img_samples": OptionInfo("outputs/text", 'Output directory for txt2img images', component_args=hide_dirs), + "outdir_img2img_samples": OptionInfo("outputs/image", 'Output directory for img2img images', component_args=hide_dirs), + "outdir_extras_samples": OptionInfo("outputs/extras", 'Output directory for images from extras tab', component_args=hide_dirs), + "outdir_grids": OptionInfo("", "Output directory for grids; if empty, defaults to two directories below", component_args=hide_dirs), + "outdir_txt2img_grids": OptionInfo("outputs/grids", 'Output directory for txt2img grids', component_args=hide_dirs), + "outdir_img2img_grids": OptionInfo("outputs/grids", 'Output directory for img2img grids', component_args=hide_dirs), + "outdir_save": OptionInfo("outputs/save", "Directory for saving images using the Save button", component_args=hide_dirs), + "outdir_init_images": OptionInfo("outputs/init-images", "Directory for saving init images when using img2img", component_args=hide_dirs), +})) + +options_templates.update(options_section(('ui', "User interface"), { + "gradio_theme": OptionInfo("black-orange", "UI theme", gr.Dropdown, lambda: {"choices": list_themes()}, refresh=refresh_themes), + "theme_style": OptionInfo("Auto", "Theme mode", gr.Radio, {"choices": ["Auto", "Dark", "Light"]}), + "return_grid": OptionInfo(True, "Show grid in results for web"), + "return_mask": OptionInfo(False, "For inpainting, include the greyscale mask in results for web"), + "return_mask_composite": OptionInfo(False, "For inpainting, include masked composite in results for web"), + "disable_weights_auto_swap": OptionInfo(True, "Do not change the selected model when reading generation parameters"), + "send_seed": OptionInfo(True, "Send seed when sending prompt or image to other interface"), + "send_size": OptionInfo(True, "Send size when sending prompt or image to another interface"), + "font": OptionInfo("", "Font for image grids that have text"), + "keyedit_precision_attention": OptionInfo(0.1, "Ctrl+up/down precision when editing (attention:1.1)", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}), + "keyedit_precision_extra": OptionInfo(0.05, "Ctrl+up/down precision when editing ", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}), + "keyedit_delimiters": OptionInfo(".,\/!?%^*;:{}=`~()", "Ctrl+up/down word delimiters"), # pylint: disable=anomalous-backslash-in-string + "quicksettings_list": OptionInfo(["sd_model_checkpoint"], "Quicksettings list", ui_components.DropdownMulti, lambda: {"choices": list(opts.data_labels.keys())}), + "hidden_tabs": OptionInfo([], "Hidden UI tabs", ui_components.DropdownMulti, lambda: {"choices": [x for x in tab_names]}), + "ui_tab_reorder": OptionInfo("From Text, From Image, Process Image", "UI tabs order"), + "ui_scripts_reorder": OptionInfo("Enable Dynamic Thresholding, ControlNet", "UI scripts order"), + "ui_reorder": OptionInfo(", ".join(ui_reorder_categories), "txt2img/img2img UI item order"), + "ui_extra_networks_tab_reorder": OptionInfo("Checkpoints, Lora, LyCORIS, Textual Inversion, Hypernetworks", "Extra networks tab order"), +})) + +options_templates.update(options_section(('live-preview', "Live previews"), { + "show_progressbar": OptionInfo(True, "Show progressbar"), + "live_previews_enable": OptionInfo(True, "Show live previews of the created image"), + "show_progress_grid": OptionInfo(True, "Show previews of all images generated in a batch as a grid"), + "notification_audio_enable": OptionInfo(False, "Play a sound when images are finished generating"), + "notification_audio_path": OptionInfo("html/notification.mp3","Path to notification sound", component_args=hide_dirs), + "show_progress_every_n_steps": OptionInfo(1, "Live preview display period", gr.Slider, {"minimum": -1, "maximum": 32, "step": 1}), + "show_progress_type": OptionInfo("TAESD", "Live preview method", gr.Radio, {"choices": ["Full VAE", "Approximate NN", "Approximate simple", "TAESD"]}), + "live_preview_content": OptionInfo("Combined", "Live preview subject", gr.Radio, {"choices": ["Combined", "Prompt", "Negative prompt"]}), + "live_preview_refresh_period": OptionInfo(250, "Progressbar/preview update period, in milliseconds") +})) + +options_templates.update(options_section(('sampler-params', "Sampler Settings"), { + "show_samplers": OptionInfo(["Euler a", "UniPC", "DDIM", "DPM++ 2M SDE", "DPM++ 2M SDE Karras", "DPM2 Karras", "DPM++ 2M Karras"], "Show samplers in user interface", gr.CheckboxGroup, lambda: {"choices": [x.name for x in list_samplers() if x.name != "PLMS"]}), + "fallback_sampler": OptionInfo("Euler a", "Secondary sampler", gr.Dropdown, lambda: {"choices": ["None"] + [x.name for x in list_samplers()]}), + "force_latent_sampler": OptionInfo("None", "Force latent upscaler sampler", gr.Dropdown, lambda: {"choices": ["None"] + [x.name for x in list_samplers()]}), + "eta_ancestral": OptionInfo(1.0, "Noise multiplier for ancestral samplers (eta)", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), + "eta_ddim": OptionInfo(0.0, "Noise multiplier for DDIM (eta)", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), + "ddim_discretize": OptionInfo('uniform', "DDIM discretize img2img", gr.Radio, {"choices": ['uniform', 'quad']}), + 's_churn': OptionInfo(0.0, "sigma churn", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), + 's_min_uncond': OptionInfo(0, "Negative Guidance minimum sigma", gr.Slider, {"minimum": 0.0, "maximum": 4.0, "step": 0.01}), + 's_tmin': OptionInfo(0.0, "sigma tmin", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), + 's_noise': OptionInfo(1.0, "sigma noise", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), + 'eta_noise_seed_delta': OptionInfo(0, "Noise seed delta (eta)", gr.Number, {"precision": 0}), + 'always_discard_next_to_last_sigma': OptionInfo(False, "Always discard next-to-last sigma"), + 'uni_pc_variant': OptionInfo("bh1", "UniPC variant", gr.Radio, {"choices": ["bh1", "bh2", "vary_coeff"]}), + 'uni_pc_skip_type': OptionInfo("time_uniform", "UniPC skip type", gr.Radio, {"choices": ["time_uniform", "time_quadratic", "logSNR"]}), + 'uni_pc_order': OptionInfo(3, "UniPC order (must be < sampling steps)", gr.Slider, {"minimum": 1, "maximum": 50, "step": 1}), + 'uni_pc_lower_order_final': OptionInfo(True, "UniPC lower order final"), +})) + +options_templates.update(options_section(('postprocessing', "Postprocessing"), { + 'postprocessing_enable_in_main_ui': OptionInfo([], "Enable addtional postprocessing operations", ui_components.DropdownMulti, lambda: {"choices": [x.name for x in shared_items.postprocessing_scripts()]}), + 'postprocessing_operation_order': OptionInfo([], "Postprocessing operation order", ui_components.DropdownMulti, lambda: {"choices": [x.name for x in shared_items.postprocessing_scripts()]}), + 'upscaling_max_images_in_cache': OptionInfo(5, "Maximum number of images in upscaling cache", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), +})) + +options_templates.update(options_section(('training', "Training"), { + "unload_models_when_training": OptionInfo(False, "Move VAE and CLIP to RAM when training if possible"), + "pin_memory": OptionInfo(True, "Pin training dataset to memory"), + "save_optimizer_state": OptionInfo(False, "Saves resumable optimizer state when training embedding or hypernetwork"), + "save_training_settings_to_txt": OptionInfo(True, "Save textual inversion and hypernet settings to a text file whenever training starts"), + "dataset_filename_word_regex": OptionInfo("", "Filename word regex"), + "dataset_filename_join_string": OptionInfo(" ", "Filename join string"), + "embeddings_templates_dir": OptionInfo(os.path.join(paths.script_path, 'train', 'templates'), "Embeddings train templates directory"), + "training_image_repeats_per_epoch": OptionInfo(1, "Number of repeats for a single input image per epoch; used only for displaying epoch number", gr.Number, {"precision": 0}), + "training_write_csv_every": OptionInfo(0, "Save an csv containing the loss to log directory every N steps, 0 to disable"), + "training_enable_tensorboard": OptionInfo(False, "Enable tensorboard logging"), + "training_tensorboard_save_images": OptionInfo(False, "Save generated images within tensorboard"), + "training_tensorboard_flush_every": OptionInfo(120, "How often, in seconds, to flush the pending tensorboard events and summaries to disk"), +})) + +options_templates.update(options_section(('interrogate', "Interrogate"), { + "interrogate_keep_models_in_memory": OptionInfo(False, "Interrogate: keep models in VRAM"), + "interrogate_return_ranks": OptionInfo(True, "Interrogate: include ranks of model tags matches in results"), + "interrogate_clip_num_beams": OptionInfo(1, "Interrogate: num_beams for BLIP", gr.Slider, {"minimum": 1, "maximum": 16, "step": 1}), + "interrogate_clip_min_length": OptionInfo(32, "Interrogate: minimum description length (excluding artists, etc..)", gr.Slider, {"minimum": 1, "maximum": 128, "step": 1}), + "interrogate_clip_max_length": OptionInfo(192, "Interrogate: maximum description length", gr.Slider, {"minimum": 1, "maximum": 256, "step": 1}), + "interrogate_clip_dict_limit": OptionInfo(2048, "CLIP: maximum number of lines in text file (0 = No limit)"), + "interrogate_clip_skip_categories": OptionInfo(["artists", "movements", "flavors"], "CLIP: skip inquire categories", gr.CheckboxGroup, lambda: {"choices": modules.interrogate.category_types()}, refresh=modules.interrogate.category_types), + "interrogate_deepbooru_score_threshold": OptionInfo(0.65, "Interrogate: deepbooru score threshold", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01}), + "deepbooru_sort_alpha": OptionInfo(False, "Interrogate: deepbooru sort alphabetically"), + "deepbooru_use_spaces": OptionInfo(False, "use spaces for tags in deepbooru"), + "deepbooru_escape": OptionInfo(True, "escape (\\) brackets in deepbooru (so they are used as literal brackets and not for emphasis)"), + "deepbooru_filter_tags": OptionInfo("", "filter out those tags from deepbooru output (separated by comma)"), +})) + +options_templates.update(options_section(('upscaling', "Upscaling"), { + "upscaler_for_img2img": OptionInfo("None", "Default upscaler for image resize operations", gr.Dropdown, lambda: {"choices": [x.name for x in sd_upscalers]}), + "realesrgan_enabled_models": OptionInfo(["R-ESRGAN 4x+", "R-ESRGAN 4x+ Anime6B"], "Real-ESRGAN available models", gr.CheckboxGroup, lambda: {"choices": shared_items.realesrgan_models_names()}), + "ESRGAN_tile": OptionInfo(192, "Tile size for ESRGAN upscalers (0 = no tiling)", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}), + "ESRGAN_tile_overlap": OptionInfo(8, "Tile overlap in pixels for ESRGAN upscalers", gr.Slider, {"minimum": 0, "maximum": 48, "step": 1}), + "SCUNET_tile": OptionInfo(256, "Tile size for SCUNET upscalers (0 = no tiling)", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}), + "SCUNET_tile_overlap": OptionInfo(8, "Tile overlap, in pixels for SCUNET upscalers (low values = visible seam)", gr.Slider, {"minimum": 0, "maximum": 64, "step": 1}), + "use_old_hires_fix_width_height": OptionInfo(False, "Hires fix uses width & height to set final resolution rather than first pass"), + "dont_fix_second_order_samplers_schedule": OptionInfo(False, "Do not fix prompt schedule for second order samplers"), +})) + +options_templates.update(options_section(('lora', "Lora"), { + "lyco_patch_lora": OptionInfo(False, "Use LyCoris handler for all Lora types", gr.Checkbox, { "visible": True }), + "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, { "visible": True }), +})) + +options_templates.update(options_section(('face-restoration', "Face restoration"), { + "face_restoration_model": OptionInfo("CodeFormer", "Face restoration model", gr.Radio, lambda: {"choices": [x.name() for x in face_restorers]}), + "code_former_weight": OptionInfo(0.2, "CodeFormer weight parameter; 0 = maximum effect; 1 = minimum effect", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01}), + "face_restoration_unload": OptionInfo(False, "Move face restoration model from VRAM into RAM after processing"), +})) + +options_templates.update(options_section(('extra_networks', "Extra Networks"), { + "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"), + "sd_hypernetwork": OptionInfo("None", "Add hypernetwork to prompt", gr.Dropdown, lambda: {"choices": ["None"] + [x for x in hypernetworks.keys()]}, refresh=reload_hypernetworks), +})) + +options_templates.update(options_section(('token_merging', 'Token Merging'), { + "token_merging": OptionInfo(False, "Enable redundant token merging via tomesd for speed and memory improvements", gr.Checkbox), + "token_merging_ratio": OptionInfo(0.5, "Token merging Ratio. Higher merging ratio = faster generation, smaller VRAM usage, lower quality", gr.Slider, {"minimum": 0, "maximum": 0.9, "step": 0.1}), + "token_merging_hr_only": OptionInfo(True, "Apply only to high-res fix pass. Disabling can yield a ~20-35% speedup on contemporary resolutions", gr.Checkbox), + "token_merging_ratio_hr": OptionInfo(0.5, "Merging Ratio for high-res pass", gr.Slider, {"minimum": 0, "maximum": 0.9, "step": 0.1}), + "token_merging_random": OptionInfo(False, "Use random perturbations - Can improve outputs for certain samplers. For others, it may cause visual artifacting", gr.Checkbox), + "token_merging_merge_attention": OptionInfo(True, "Merge attention (Recommend on)", gr.Checkbox), + "token_merging_merge_cross_attention": OptionInfo(False, "Merge cross attention (Recommend off)", gr.Checkbox), + "token_merging_merge_mlp": OptionInfo(False, "Merge mlp (Strongly recommend off)", gr.Checkbox), + "token_merging_maximum_down_sampling": OptionInfo(1, "Maximum down sampling", gr.Radio, lambda: {"choices": [1, 2, 4, 8]}), + "token_merging_stride_x": OptionInfo(2, "Stride - X", gr.Slider, {"minimum": 2, "maximum": 8, "step": 2}), + "token_merging_stride_y": OptionInfo(2, "Stride - Y", gr.Slider, {"minimum": 2, "maximum": 8, "step": 2}) +})) + +options_templates.update(options_section((None, "Hidden options"), { + "disabled_extensions": OptionInfo([], "Disable these extensions"), + "disable_all_extensions": OptionInfo("none", "Disable all extensions (preserves the list of disabled extensions)", gr.Radio, {"choices": ["none", "user", "all"]}), + "sd_checkpoint_hash": OptionInfo("", "SHA256 hash of the current checkpoint"), +})) + +options_templates.update() + + +class Options: + data = None + data_labels = options_templates + typemap = {int: float} + + def __init__(self): + self.data = {k: v.default for k, v in self.data_labels.items()} + + def __setattr__(self, key, value): + if self.data is not None: + if key in self.data or key in self.data_labels: + if cmd_opts.freeze: + log.warning(f'Settings are frozen: {key}') + return + if cmd_opts.hide_ui_dir_config and key in restricted_opts: + log.warning(f'Settings key is restricted: {key}') + return + else: + self.data[key] = value + return + + return super(Options, self).__setattr__(key, value) + + def __getattr__(self, item): + if self.data is not None: + if item in self.data: + return self.data[item] + if item in self.data_labels: + return self.data_labels[item].default + return super(Options, self).__getattribute__(item) + + def set(self, key, value): + """sets an option and calls its onchange callback, returning True if the option changed and False otherwise""" + oldval = self.data.get(key, None) + if oldval == value: + return False + try: + setattr(self, key, value) + except RuntimeError: + return False + if self.data_labels[key].onchange is not None: + try: + self.data_labels[key].onchange() + except Exception as e: + errors.display(e, f"changing setting {key} to {value}") + setattr(self, key, oldval) + return False + return True + + def get_default(self, key): + """returns the default value for the key""" + data_label = self.data_labels.get(key) + if data_label is None: + return None + return data_label.default + + def save(self, filename): + if cmd_opts.freeze: + log.warning(f'Settings saving is disabled: {filename}') + return + with open(filename, "w", encoding="utf8") as file: + json.dump(self.data, file, indent=4) + + def same_type(self, x, y): + if x is None or y is None: + return True + type_x = self.typemap.get(type(x), type(x)) + type_y = self.typemap.get(type(y), type(y)) + return type_x == type_y + + def load(self, filename): + if not os.path.isfile(filename): + log.debug(f'Created default config: {filename}') + self.save(filename) + return + with open(filename, "r", encoding="utf8") as file: + self.data = json.load(file) + if self.data.get('quicksettings') is not None and self.data.get('quicksettings_list') is None: + self.data['quicksettings_list'] = [i.strip() for i in self.data.get('quicksettings').split(',')] + bad_settings = 0 + for k, v in self.data.items(): + info = self.data_labels.get(k, None) + if info is not None and not self.same_type(info.default, v): + log.error(f"Warning: bad setting value: {k}: {v} ({type(v).__name__}; expected {type(info.default).__name__})") + bad_settings += 1 + if bad_settings > 0: + log.error(f"Error: Bad settings found in {filename}") + + def onchange(self, key, func, call=True): + item = self.data_labels.get(key) + item.onchange = func + if call: + func() + + def dumpjson(self): + d = {k: self.data.get(k, self.data_labels.get(k).default) for k in self.data_labels.keys()} + metadata = { + k: { + "is_stored": k in self.data, + "tab_name": v.section[0] + } for k, v in self.data_labels.items() + } + return json.dumps({"values": d, "metadata": metadata}) + + def add_option(self, key, info): + self.data_labels[key] = info + + def reorder(self): + """reorder settings so that all items related to section always go together""" + section_ids = {} + settings_items = self.data_labels.items() + for k, item in settings_items: + if item.section not in section_ids: + section_ids[item.section] = len(section_ids) + self.data_labels = {k: v for k, v in sorted(settings_items, key=lambda x: section_ids[x[1].section])} + + def cast_value(self, key, value): + """casts an arbitrary to the same type as this setting's value with key + Example: cast_value("eta_noise_seed_delta", "12") -> returns 12 (an int rather than str) + """ + if value is None: + return None + default_value = self.data_labels[key].default + if default_value is None: + default_value = getattr(self, key, None) + if default_value is None: + return None + expected_type = type(default_value) + if expected_type == bool and value == "False": + value = False + elif expected_type == type(value): + pass + else: + value = expected_type(value) + return value + + +opts = Options() +config_filename = cmd_opts.config +opts.load(config_filename) +cmd_opts = cmd_args.compatibility_args(opts, cmd_opts) +if cmd_opts.backend == 'diffusers': + log.info('Overriding backend to Diffusers') + opts.data['sd_backend'] = 'Diffusers' +if cmd_opts.backend == 'original': + log.info('Overriding backend to Diffusers') + opts.data['sd_backend'] = 'Original' +backend = Backend.DIFFUSERS if opts.sd_backend == 'Diffusers' else Backend.ORIGINAL + +prompt_styles = modules.styles.StyleDatabase(opts.styles_dir) +cmd_opts.disable_extension_access = (cmd_opts.share or cmd_opts.listen or (cmd_opts.server_name or False)) and not cmd_opts.insecure +devices.device, devices.device_interrogate, devices.device_gfpgan, devices.device_esrgan, devices.device_codeformer = (devices.cpu if any(y in cmd_opts.use_cpu for y in [x, 'all']) else devices.get_optimal_device() for x in ['sd', 'interrogate', 'gfpgan', 'esrgan', 'codeformer']) +device = devices.device +batch_cond_uncond = opts.always_batch_cond_uncond or not (cmd_opts.lowvram or cmd_opts.medvram) +parallel_processing_allowed = not cmd_opts.lowvram and not cmd_opts.medvram +mem_mon = modules.memmon.MemUsageMonitor("MemMon", device, opts) +mem_mon.start() +if device.type == 'privateuseone': + import modules.dml # pylint: disable=ungrouped-imports + + +def reload_gradio_theme(theme_name=None): + global gradio_theme # pylint: disable=global-statement + if not theme_name: + theme_name = opts.gradio_theme + default_font_params = {} + res = 0 + try: + req = urllib.request.Request("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono", method="HEAD") + res = urllib.request.urlopen(req, timeout=3.0).status + except: + res = 0 + if res != 200: + log.info('No internet access detected, using default fonts') + default_font_params = { + 'font':['Helvetica', 'ui-sans-serif', 'system-ui', 'sans-serif'], + 'font_mono':['IBM Plex Mono', 'ui-monospace', 'Consolas', 'monospace'] + } + if theme_name == "black-orange": + gradio_theme = gr.themes.Default(**default_font_params) + elif theme_name.startswith("gradio/"): + if theme_name == "gradio/default": + gradio_theme = gr.themes.Default(**default_font_params) + if theme_name == "gradio/base": + gradio_theme = gr.themes.Base(**default_font_params) + if theme_name == "gradio/glass": + gradio_theme = gr.themes.Glass(**default_font_params) + if theme_name == "gradio/monochrome": + gradio_theme = gr.themes.Monochrome(**default_font_params) + if theme_name == "gradio/soft": + gradio_theme = gr.themes.Soft(**default_font_params) + else: + try: + gradio_theme = gr.themes.ThemeClass.from_hub(theme_name) + except: + log.error("Theme download error accessing HuggingFace") + gradio_theme = gr.themes.Default(**default_font_params) + log.info(f'Loading UI theme: name={theme_name} style={opts.theme_style}') + + +class TotalTQDM: + def __init__(self): + self._tqdm = None + + def reset(self): + self._tqdm = tqdm.tqdm( + desc="Total", + total=state.job_count * state.sampling_steps, + position=1, + ) + + def update(self): + if not opts.multiple_tqdm or cmd_opts.disable_console_progressbars: + return + if self._tqdm is None: + self.reset() + self._tqdm.update() + + def updateTotal(self, new_total): + if not opts.multiple_tqdm or cmd_opts.disable_console_progressbars: + return + if self._tqdm is None: + self.reset() + self._tqdm.total = new_total + + def clear(self): + if self._tqdm is not None: + self._tqdm.refresh() + self._tqdm.close() + self._tqdm = None + +total_tqdm = TotalTQDM() + + +def restart_server(restart=True): + if demo is None: + return + log.info('Server shutdown requested') + try: + demo.server.wants_restart = restart + demo.server.should_exit = True + demo.server.force_exit = True + demo.close(verbose=False) + demo.server.close() + demo.fns = [] + except: + pass + if restart: + log.info('Server will restart') + + +def restore_defaults(restart=True): + if os.path.exists(cmd_opts.config): + log.info('Restoring server defaults') + os.remove(cmd_opts.config) + if os.path.exists(cmd_opts.ui_config): + log.info('Restoring UI defaults') + os.remove(cmd_opts.ui_config) + restart_server(restart) + + +def listfiles(dirname): + filenames = [os.path.join(dirname, x) for x in sorted(os.listdir(dirname), key=str.lower) if not x.startswith(".")] + return [file for file in filenames if os.path.isfile(file)] + + +def walk_files(path, allowed_extensions=None): + if not os.path.exists(path): + return + if allowed_extensions is not None: + allowed_extensions = set(allowed_extensions) + for root, _dirs, files in os.walk(path, followlinks=True): + for filename in files: + if allowed_extensions is not None: + _, ext = os.path.splitext(filename) + if ext not in allowed_extensions: + continue + yield os.path.join(root, filename) + + +def html_path(filename): + return os.path.join(paths.script_path, "html", filename) + + +def html(filename): + path = html_path(filename) + if os.path.exists(path): + with open(path, encoding="utf8") as file: + return file.read() + return "" + + +def get_version(): + version = None + if version is None: + try: + import subprocess + res = subprocess.run('git log --pretty=format:"%h %ad" -1 --date=short', stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell=True, check=True) + ver = res.stdout.decode(encoding = 'utf8', errors='ignore') if len(res.stdout) > 0 else ' ' + githash, updated = ver.split(' ') + res = subprocess.run('git remote get-url origin', stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell=True, check=True) + origin = res.stdout.decode(encoding = 'utf8', errors='ignore') if len(res.stdout) > 0 else '' + res = subprocess.run('git branch --show-current', stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell=True, check=True) + branch = res.stdout.decode(encoding = 'utf8', errors='ignore') if len(res.stdout) > 0 else '' + version = { + 'app': 'sd.next', + 'updated': updated, + 'hash': githash, + 'url': origin.replace('\n', '') + '/tree/' + branch.replace('\n', '') + } + except: + version = { 'app': 'sd.next' } + return version + + +class Shared(sys.modules[__name__].__class__): + # this class is here to provide sd_model field as a property, so that it can be created and loaded on demand rather than at program startup. + sd_model_val = None + + @property + def sd_model(self): + import modules.sd_models # pylint: disable=W0621 + # return modules.sd_models.model_data.sd_model + return modules.sd_models.model_data.get_sd_model() + + @sd_model.setter + def sd_model(self, value): + import modules.sd_models # pylint: disable=W0621 + modules.sd_models.model_data.set_sd_model(value) + +# sd_model: LatentDiffusion = None # this var is here just for IDE's type checking; it cannot be accessed because the class field above will be accessed instead +sd_model = None +sys.modules[__name__].__class__ = Shared From cba227f19c5de4ba83d427040b1dd18e9864887a Mon Sep 17 00:00:00 2001 From: Alex Heller Date: Fri, 9 Jun 2023 22:06:15 +0200 Subject: [PATCH 31/31] Removed extraneous space added accidentally --- modules/shared.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/shared.py b/modules/shared.py index 04fc3c994..f3b24535e 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -330,7 +330,7 @@ options_templates.update(options_section(('cuda', "Compute Settings"), { })) options_templates.update(options_section(('system-paths', "System Paths"), { - "temp_dir": OptionInfo("", "Directory for temporary images; leave empty for default"), + "temp_dir": OptionInfo("", "Directory for temporary images; leave empty for default"), "clean_temp_dir_at_start": OptionInfo(True, "Cleanup non-default temporary directory when starting webui"), "ckpt_dir": OptionInfo(os.path.join(paths.models_path, 'Stable-diffusion'), "Path to directory with stable diffusion checkpoints"), "diffusers_dir": OptionInfo(os.path.join(paths.models_path, 'Diffusers'), "Path to directory with stable diffusion diffusers"),