From 231ca1ac7e7ceb1655b4a0c0251198556fb1351c Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 18 Oct 2023 11:37:30 -0400 Subject: [PATCH] fixes for zero-day issues --- CHANGELOG.md | 22 +++++ extensions-builtin/Lora/networks.py | 9 +- html/locale_en.json | 2 +- installer.py | 2 +- javascript/base.css | 125 +++++++++++++++++++++++++++ javascript/{style.css => sdnext.css} | 2 +- modules/cmd_args.py | 2 +- modules/sd_hijack_freeu.py | 32 +++---- modules/sd_models.py | 30 +++---- modules/shared.py | 90 +------------------ modules/styles.py | 2 +- modules/theme.py | 88 +++++++++++++++++++ modules/ui.py | 15 ++-- requirements.txt | 4 +- scripts/xyz_grid.py | 43 +++++---- webui.py | 2 +- wiki | 2 +- 17 files changed, 319 insertions(+), 153 deletions(-) create mode 100644 javascript/base.css rename javascript/{style.css => sdnext.css} (98%) create mode 100644 modules/theme.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e5765563..74d998f15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ # Change Log for SD.Next +## Update for 2023-10-18 + +Service release addressing all zero-day issues reported so far... + +**Fixes** +- complete lyco removal +- fix freeu for backend original and add it to xyz grid +- fix loading diffuser models in huggingface format from non-standard location +- fix default styles looking in wrong location +- update `typing-extensions` + +**Themes** +- all built-in themes are fully supported: + - *black-teal (default), light-teal, black-orange, invoked, amethyst-nightfall, midnight-barbie* +- if you're using any **gradio default** themes or a **3rd party** theme or that are not optimized for SD.Next, you may experience issues + default minimal style has been updated for compatibility, but actual styling is completely outside of SD.Next control + +**Cannot reproduce** +- diffusers: img2img: batch count 2 => black images for higher batches +- diffusers deis sampler reporting sde-dpmsolver++ is not implemented + + ## Update for 2023-10-17 This is a major release, with many changes and new functionality... diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index 4edcf986b..2132c8ed5 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -374,8 +374,13 @@ def list_available_networks(): available_network_hash_lookup.clear() forbidden_network_aliases.update({"none": 1, "Addams": 1}) os.makedirs(shared.cmd_opts.lora_dir, exist_ok=True) - candidates = list(shared.walk_files(shared.cmd_opts.lora_dir, allowed_extensions=[".pt", ".ckpt", ".safetensors"])) - candidates += list(shared.walk_files(shared.cmd_opts.lyco_dir, allowed_extensions=[".pt", ".ckpt", ".safetensors"])) + candidates = [] + if os.path.exists(shared.cmd_opts.lora_dir): + candidates += list(shared.walk_files(shared.cmd_opts.lora_dir, allowed_extensions=[".pt", ".ckpt", ".safetensors"])) + else: + shared.log.warning('LoRA directory not found: path={shared.cmd_opts.lora_dir}') + if os.path.exists(shared.cmd_opts.lyco_dir): + candidates += list(shared.walk_files(shared.cmd_opts.lyco_dir, allowed_extensions=[".pt", ".ckpt", ".safetensors"])) for filename in candidates: if os.path.isdir(filename): continue diff --git a/html/locale_en.json b/html/locale_en.json index 92b41bf39..e1a6cc93c 100644 --- a/html/locale_en.json +++ b/html/locale_en.json @@ -59,7 +59,7 @@ {"id":"","label":"Skip","localized":"","hint":"Stop processing current job and continue processing"}, {"id":"","label":"Interrupt","localized":"","hint":"Interrupt current processing job"}, {"id":"","label":"Pause","localized":"","hint":"Pause processing"}, - {"id":"","label":"Apply","localized":"","hint":"Apply parameters from last known generated image"}, + {"id":"","label":"Restore","localized":"","hint":"Restore parameters from last known generated image"}, {"id":"","label":"Clear","localized":"","hint":"Clear prompts"}, {"id":"","label":"Networks","localized":"","hint":"Open extra network interface"}, {"id":"","label":"Interrogate\nCLIP","localized":"","hint":"Run interrogate using CLIP model"}, diff --git a/installer.py b/installer.py index 36121cdba..83221fc7a 100644 --- a/installer.py +++ b/installer.py @@ -447,7 +447,7 @@ def check_torch(): log.info('Using CPU-only Torch') torch_command = os.environ.get('TORCH_COMMAND', 'torch torchvision') if 'torch' in torch_command and not args.version: - log.info('Installing torch - this may take a while') + # log.info('Installing torch - this may take a while') install(torch_command, 'torch torchvision') else: try: diff --git a/javascript/base.css b/javascript/base.css new file mode 100644 index 000000000..86ef0f2a7 --- /dev/null +++ b/javascript/base.css @@ -0,0 +1,125 @@ +/* toolbutton */ +.gradio-button.tool { max-width: min-content; min-width: min-content !important; align-self: end; font-size: 1.4em; color: var(--body-text-color) !important; margin-bottom: var(--spacing-md); } + +/* token counters */ +.block.token-counter{ position: absolute; display: inline-block; right: 0; min-width: 0 !important; width: auto; z-index: 100; top: 0; } +.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; } +.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; } + +/* live preview */ +.progressDiv{ position: relative; height: 20px; background: #b4c0cc; 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; overflow: visible; white-space: nowrap; padding: 0 0.5em; } +.livePreview { position: absolute; z-index: 50; background-color: transparent; width: -moz-available; width: -webkit-fill-available; } +.livePreview img { position: absolute; object-fit: contain; width: 100%; height: 100%; } +.dark .livePreview { background-color: rgb(17 24 39 / var(--tw-bg-opacity)); } +.popup-metadata { color: white; background: #0000; display: inline-block; white-space: pre-wrap; font-size: 0.75em; } +.global-popup{ display: flex; position: fixed; z-index: 10001; 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.5em; 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.75); backdrop-filter: blur(6px); + user-select: none; -webkit-user-select: none; flex-direction: row; } +.modalControls { display: flex; justify-content: space-evenly; background-color: transparent; position: absolute; width: 99%; z-index: 1; } +.modalControls:hover { background-color: #50505050; } +.modalControls span { color: white; font-size: 2em; font-weight: bold; cursor: pointer; filter: grayscale(100%); } +.modalControls span:hover, .modalControls span:focus { color: var(--highlight-color); filter: none; } +.lightboxModalPreviewZone { display: flex; width: 100%; height: 100%; } +.lightboxModalPreviewZone:focus-visible { outline: none; } +.lightboxModalPreviewZone > img { display: block; margin: auto; width: auto; } +.lightboxModalPreviewZone > img.modalImageFullscreen{ object-fit: contain; height: 100%; width: 100%; min-height: 0; background: transparent; } +table.settings-value-table { background: white; border-collapse: collapse; margin: 1em; border: var(--spacing-sm) solid white; } +table.settings-value-table td { padding: 0.4em; border: 1px solid #ccc; max-width: 36em; } +.modalPrev, .modalNext { cursor: pointer; position: relative; z-index: 1; 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; user-select: none; -webkit-user-select: none; } +.modalNext { right: 0; } +.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: var(--spacing-md); border: 2px solid var(--highlight-color); background: var(--background-fill-primary); color: var(--body-text-color); } +.context-menu-items { list-style: none; margin: 0; padding: 0; } +.context-menu-items a { display: block; padding: var(--spacing-md); cursor: pointer; font-weight: normal; } +.context-menu-items a:hover { background: var(--highlight-color) } + +/* log monitor */ +.log-monitor { display: none; justify-content: unset !important; overflow: hidden; padding: 0; margin-top: auto; font-family: monospace; font-size: 0.85em; } +.log-monitor td, .log-monitor th { padding-left: 1em; } + +/* changelog */ +.md h2 { background-color: var(--background-fill-primary); padding: 0.5em; } +.md ul { list-style-type: square !important; text-indent: 1em; margin-left: 4em; } +.md li { list-style-position: outside !important; text-indent: 0; } +.md p { margin-left: 2em; } + +/* extensions */ +#tab_extensions table, #tab_config table{ border-collapse: collapse; } +#tab_extensions table td, #tab_extensions table th, #tab_config table td, #tab_config 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%; } +.extension-button { font-size: 95% !important; width: 6em; } +#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%; } + +/* extra networks */ +.extra-networks > div { margin: 0; gap: 0.2em; border-bottom: none !important; } +.extra-networks .second-line { display: flex; width: -moz-available; width: -webkit-fill-available; gap: 0.3em; box-shadow: var(--input-shadow); } +.extra-networks .search { flex: 1; } +.extra-networks .description { flex: 3; } +.extra-networks .tab-nav > button { margin-right: 0; height: 24px; padding: 2px 4px 2px 4px; } +.extra-networks .buttons { position: absolute; right: 0; margin: -4px; background: var(--background-color); } +.extra-networks .buttons>button { height: 1.2em; margin-top: var(--spacing-md); } +.extra-networks .custom-button { width: 120px; width: 100%; background: none; justify-content: left; text-align: left; padding: 2px 8px 2px 16px; text-indent: -8px; box-shadow: none; line-break: auto; } +.extra-networks .custom-button:hover { background: var(--button-primary-background-fill) } +.extra-networks-tab { padding: 0 !important; } +.extra-network-subdirs { background: var(--input-background-fill); overflow-x: hidden; overflow-y: auto; min-width: max(15%, 120px); padding-top: 0.5em; margin-top: -4px !important; } +.extra-networks-page { display: flex } +.extra-network-cards { display: flex; flex-wrap: wrap; overflow-y: auto; overflow-x: hidden; align-content: flex-start; width: -moz-available; width: -webkit-fill-available; } +.extra-network-cards .card { height: fit-content; margin: 0 0 0.5em 0.5em; position: relative; scroll-snap-align: start; scroll-margin-top: 0; } +.extra-network-cards .card .overlay { position: absolute; bottom: 0; padding: 0.2em; z-index: 10; width: 100%; background: none; } +.extra-network-cards .card .overlay .name { font-size: 1.1em; font-weight: bold; text-shadow: 1px 1px black; color: white; overflow-wrap: break-word; } +.extra-network-cards .card .preview { box-shadow: var(--button-shadow); min-height: 30px; } +.extra-network-cards .card:hover .overlay { background: rgba(0, 0, 0, 0.40); } +.extra-network-cards .card:hover .preview { box-shadow: none; filter: grayscale(100%); } +.extra-network-cards .card:hover .overlay { background: rgba(0, 0, 0, 0.40); } +.extra-network-cards .card .overlay .tags { margin: 4px; display: none; overflow-wrap: break-word; } +.extra-network-cards .card .overlay .tag { padding: 2px; margin: 2px; background: var(--neutral-700); cursor: pointer; display: inline-block; } +.extra-network-cards .card .actions > span { padding: 4px; } +.extra-network-cards .card:hover .actions { display: block; } +.extra-network-cards .card:hover .overlay .tags { display: block; } +.extra-network-cards .card .actions { font-size: 3em; display: none; text-align-last: right; cursor: pointer; font-variant: unicase; position: absolute; z-index: 100; right: 0; height: 0.7em; width: 100%; background: rgba(0, 0, 0, 0.40); } +#txt2img_description, #img2img_description { max-height: 63px; overflow-y: auto !important; } +#txt2img_description > label > textarea, #img2img_description > label > textarea { font-size: 0.9em } + +#txt2img_extra_details > div, #img2img_extra_details > div { overflow-y: auto; min-height: 40vh; max-height: 80vh; align-self: flex-start; } +#txt2img_extra_details, #img2img_extra_details { position: fixed; bottom: 50%; left: 50%; transform: translate(-50%, 50%); padding: 0.8em; border: var(--block-border-width) solid var(--highlight-color) !important; + z-index: 100; box-shadow: var(--button-shadow); } +#txt2img_extra_details td:first-child, #img2img_extra_details td:first-child { font-weight: bold; vertical-align: top; } +#txt2img_extra_details .gradio-image, #img2img_extra_details .gradio-image { max-height: 70vh; } + +/* custom component */ +.folder-selector textarea { height: 2em !important; padding: 6px !important; } +.nvml { position: fixed; bottom: 10px; right: 10px; background: var(--background-fill-primary); border: 1px solid var(--button-primary-border-color); padding: 6px; color: var(--button-primary-text-color); + font-size: 0.7em; z-index: 50; font-family: monospace; display: none; } + +/* loader */ +.splash { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 1000; display: block; text-align: center; } +.motd { margin-top: 2em; color: var(--body-text-color-subdued); font-family: monospace; font-variant: all-petite-caps; } +.splash-img { margin: 10% auto 0 auto; width: 512px; background-repeat: no-repeat; height: 512px; animation: color 10s infinite alternate; } +.loading { color: white; position: absolute; top: 20%; left: 50%; transform: translateX(-50%); } +.loader { width: 300px; height: 300px; border: var(--spacing-md) solid transparent; border-radius: 50%; border-top: var(--spacing-md) solid var(--primary-600); animation: spin 4s linear infinite; position: relative; } +.loader::before, .loader::after { content: ""; position: absolute; top: 6px; bottom: 6px; left: 6px; right: 6px; border-radius: 50%; border: var(--spacing-md) solid transparent; } +.loader::before { border-top-color: var(--primary-900); animation: 3s spin linear infinite; } +.loader::after { border-top-color: var(--primary-300); animation: spin 1.5s linear infinite; } +@keyframes move { from { background-position-x: 0, -40px; } to { background-position-x: 0, 40px; } } +@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } +@keyframes color { from { filter: hue-rotate(0deg) } to { filter: hue-rotate(360deg) } } diff --git a/javascript/style.css b/javascript/sdnext.css similarity index 98% rename from javascript/style.css rename to javascript/sdnext.css index 217796950..fb6a198f3 100644 --- a/javascript/style.css +++ b/javascript/sdnext.css @@ -1,4 +1,4 @@ -:root { --checkbox-label-gap: 0.25em 0.1em; --section-header-text-size: 12pt; --block-background-fill: transparent;} +:root { --left-column: 490px; } a { font-weight: bold; cursor: pointer; } h2 { margin-top: 1em !important; font-size: 1.4em !important; } footer { display: none; } diff --git a/modules/cmd_args.py b/modules/cmd_args.py index 6459204d1..5570bd02a 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -94,7 +94,7 @@ def compatibility_args(opts, args): group.add_argument("--lyco-dir", help=argparse.SUPPRESS, default=opts.lyco_dir) group.add_argument("--embeddings-dir", help=argparse.SUPPRESS, default=opts.embeddings_dir) group.add_argument("--hypernetwork-dir", help=argparse.SUPPRESS, default=opts.hypernetwork_dir) - group.add_argument("--lyco-patch-lora", help=argparse.SUPPRESS, default=opts.lyco_patch_lora) + group.add_argument("--lyco-patch-lora", help=argparse.SUPPRESS, default=False) group.add_argument("--lyco-debug", help=argparse.SUPPRESS, action='store_true', default=False) group.add_argument("--enable-console-prompts", help=argparse.SUPPRESS, action='store_true', default=False) group.add_argument("--safe", help=argparse.SUPPRESS, action='store_true', default=False) diff --git a/modules/sd_hijack_freeu.py b/modules/sd_hijack_freeu.py index 4d21601d4..ef893d2f3 100644 --- a/modules/sd_hijack_freeu.py +++ b/modules/sd_hijack_freeu.py @@ -1,7 +1,7 @@ import math import functools import torch -from modules.shared import state, log, opts +from modules import shared from modules.sd_hijack_unet import th # based on @@ -23,7 +23,7 @@ cat_original = None def to_denoising_step(number, steps=None) -> int: if steps is None: - steps = state.sampling_steps + steps = shared.state.sampling_steps if isinstance(number, float): return int(number * steps) return number @@ -34,11 +34,11 @@ def get_schedule_ratio(): stop_step = to_denoising_step(stop_ratio) if start_step == stop_step: smooth_schedule_ratio = 0.0 - elif state.sampling_step < start_step: - smooth_schedule_ratio = min(1.0, max(0.0, state.sampling_step / start_step)) + elif shared.state.sampling_step < start_step: + smooth_schedule_ratio = min(1.0, max(0.0, shared.state.sampling_step / start_step)) else: - smooth_schedule_ratio = min(1.0, max(0.0, 1 + (state.sampling_step - start_step) / (start_step - stop_step))) - flat_schedule_ratio = 1.0 if start_step <= state.sampling_step < stop_step else 0.0 + smooth_schedule_ratio = min(1.0, max(0.0, 1 + (shared.state.sampling_step - start_step) / (start_step - stop_step))) + flat_schedule_ratio = 1.0 if start_step <= shared.state.sampling_step < stop_step else 0.0 return lerp(flat_schedule_ratio, smooth_schedule_ratio, transition_smoothness) @@ -47,7 +47,7 @@ def lerp(a, b, r): def free_u_cat_hijack(hs, *args, original_function, **kwargs): - if not opts.freeu_enabled: + if not shared.opts.freeu_enabled: return original_function(hs, *args, **kwargs) schedule_ratio = get_schedule_ratio() if schedule_ratio == 0: @@ -67,8 +67,8 @@ def free_u_cat_hijack(hs, *args, original_function, **kwargs): mask = (region_begin <= mask) & (mask <= region_end) if region_inverted: mask = ~mask - backbone_factor = opts.freeu_b1 if index == 0 else opts.freeu_b2 - skip_factor = opts.freeu_s1 if index == 0 else opts.freeu_s2 + backbone_factor = shared.opts.freeu_b1 if index == 0 else shared.opts.freeu_b2 + skip_factor = shared.opts.freeu_s1 if index == 0 else shared.opts.freeu_s2 h[:, mask] *= lerp(1, backbone_factor, schedule_ratio) h_skip = filter_skip(h_skip, threshold=skip_cutoff, scale=lerp(1, skip_factor, schedule_ratio), scale_high=lerp(1, skip_high_end_factor, schedule_ratio)) return original_function([h, h_skip], *args, **kwargs) @@ -130,8 +130,8 @@ def apply_freeu(p, backend_original): global state_enabled # pylint: disable=global-statement global cat_original # pylint: disable=global-statement if backend_original: - if opts.freeu_enabled: - p.extra_generation_params['FreeU'] = f'b1={opts.freeu_b1} b2={opts.freeu_b2} s1={opts.freeu_s1} s2={opts.freeu_s2}' + if shared.opts.freeu_enabled: + p.extra_generation_params['FreeU'] = f'b1={shared.opts.freeu_b1} b2={shared.opts.freeu_b2} s1={shared.opts.freeu_s1} s2={shared.opts.freeu_s2}' if not state_enabled: # otherwise already patched cat_original = th.cat th.cat = functools.partial(free_u_cat_hijack, original_function=th.cat) @@ -141,12 +141,12 @@ def apply_freeu(p, backend_original): th.cat = cat_original state_enabled = False elif hasattr(p.sd_model, 'enable_freeu'): - if opts.freeu_enabled: - p.extra_generation_params['FreeU'] = f'b1={opts.freeu_b1} b2={opts.freeu_b2} s1={opts.freeu_s1} s2={opts.freeu_s2}' - p.sd_model.enable_freeu(s1=opts.freeu_s1, s2=opts.freeu_s2, b1=opts.freeu_b1, b2=opts.freeu_b2) + if shared.opts.freeu_enabled: + p.extra_generation_params['FreeU'] = f'b1={shared.opts.freeu_b1} b2={shared.opts.freeu_b2} s1={shared.opts.freeu_s1} s2={shared.opts.freeu_s2}' + p.sd_model.enable_freeu(s1=shared.opts.freeu_s1, s2=shared.opts.freeu_s2, b1=shared.opts.freeu_b1, b2=shared.opts.freeu_b2) state_enabled = True elif state_enabled: p.sd_model.disable_freeu() state_enabled = False - if opts.freeu_enabled: - log.info(f'Applying free-u: b1={opts.freeu_b1} b2={opts.freeu_b2} s1={opts.freeu_s1} s2={opts.freeu_s2}') + if shared.opts.freeu_enabled: + shared.log.info(f'Applying free-u: b1={shared.opts.freeu_b1} b2={shared.opts.freeu_b2} s1={shared.opts.freeu_s1} s2={shared.opts.freeu_s2}') diff --git a/modules/sd_models.py b/modules/sd_models.py index 6aad35ea0..3b94ed70c 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -50,21 +50,24 @@ class CheckpointInfo: self.hash = None self.filename = filename self.type = '' - filename = os.path.abspath(filename) relname = filename - if relname.startswith(script_path): + + def rel(fn, path): try: - relname = os.path.relpath(filename, script_path) + return os.path.relpath(fn, path) except: - pass - try: - relname = os.path.relpath(filename, model_path) - except: - pass - try: - relname = os.path.relpath(filename, shared.cmd_opts.ckpt_dir) - except: - pass + return fn + + if relname.startswith(shared.opts.ckpt_dir): + relname = rel(filename, shared.opts.ckpt_dir) + elif relname.startswith(shared.opts.diffusers_dir): + relname = rel(filename, shared.opts.diffusers_dir) + elif relname.startswith(model_path): + relname = rel(filename, model_path) + elif relname.startswith(script_path): + relname = rel(filename, script_path) + else: + relname = os.path.abspath(relname) relname, ext = os.path.splitext(relname) ext = ext.lower()[1:] @@ -87,9 +90,6 @@ class CheckpointInfo: self.sha256 = repo[0]['hash'] self.type = 'diffusers' - # info = shared.readfile(self.filename, silent=True) - # if 'tags' in info: - # self.tags = info['tags'] self.shorthash = self.sha256[0:10] if self.sha256 else None self.title = self.name if self.shorthash is None else f'{self.name} [{self.shorthash}]' self.path = self.filename diff --git a/modules/shared.py b/modules/shared.py index 0b2d3f41c..fff70fa2b 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -4,7 +4,6 @@ import sys import time import json import contextlib -import urllib.request from types import SimpleNamespace from urllib.parse import urlparse from enum import Enum @@ -12,7 +11,7 @@ import requests import gradio as gr import fasteners from rich.console import Console -from modules import errors, shared_items, shared_state, cmd_args, ui_components +from modules import errors, shared_items, shared_state, cmd_args, ui_components, theme 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 from modules.dml import memory_providers, default_memory_provider, directml_do_hijack import modules.interrogate @@ -42,7 +41,6 @@ extra_networks = [] options_templates = {} hypernetworks = {} loaded_hypernetworks = [] -gradio_theme = gr.themes.Base() settings_components = None latent_upscale_default_mode = "None" latent_upscale_modes = { @@ -171,50 +169,10 @@ def temp_disable_extensions(): if ext not in opts.disabled_extensions: disabled.append(ext) log.info(f'Diffusers disabling uncompatible extensions: {disabled}') - if opts.lyco_patch_lora and backend != Backend.DIFFUSERS: - cmd_opts.lyco_dir = opts.lora_dir - if 'Lora' not in opts.disabled_extensions: - disabled.append('Lora') cmd_opts.controlnet_loglevel = 'WARNING' return disabled -def list_builtin_themes(): - files = [os.path.splitext(f)[0] for f in os.listdir('javascript') if f.endswith('.css')] - return files - - -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 = list_builtin_themes() - default = ["gradio/default", "gradio/base", "gradio/glass", "gradio/monochrome", "gradio/soft"] - external = {x['id'] for x in res if x['status'] == 'RUNNING' and 'test' not in x['id'].lower()} - log.debug(f'Themes: builtin={len(builtin)} default={len(default)} external={len(external)}') - themes = sorted(builtin) + sorted(default) + sorted(external, key=str.casefold) - return themes - - -def refresh_themes(): - try: - r = req('https://huggingface.co/datasets/freddyaboulton/gradio-theme-subdomains/resolve/main/subdomains.json') - if r.status_code == 200: - res = r.json() - fn = os.path.join('html', 'themes.json') - writefile(res, fn) - list_themes() - else: - log.error('Error refreshing UI themes') - except Exception: - log.error('Exception refreshing UI themes') - - def readfile(filename, silent=False): data = {} try: @@ -388,9 +346,9 @@ options_templates.update(options_section(('system-paths', "System Paths"), { "ckpt_dir": OptionInfo(os.path.join(paths.models_path, 'Stable-diffusion'), "Folder with stable diffusion models", folder=True), "diffusers_dir": OptionInfo(os.path.join(paths.models_path, 'Diffusers'), "Folder with Hugggingface models", folder=True), "vae_dir": OptionInfo(os.path.join(paths.models_path, 'VAE'), "Folder with VAE files", folder=True), - # "sd_lora": OptionInfo("", "Add LoRA to prompt", gr.Textbox, {"visible": False}), + "sd_lora": OptionInfo("", "Add LoRA to prompt", gr.Textbox, {"visible": False}), "lora_dir": OptionInfo(os.path.join(paths.models_path, 'Lora'), "Folder with LoRA network(s)", folder=True), - "lyco_dir": OptionInfo(os.path.join(paths.models_path, 'LyCORIS'), "Folder with LyCORIS network(s)", folder=True), + "lyco_dir": OptionInfo(os.path.join(paths.models_path, 'LyCORIS'), "Folder with LyCORIS network(s)", gr.Text, {"visible": False}), "styles_dir": OptionInfo(os.path.join(paths.data_path, 'styles.csv'), "File or Folder with user-defined styles", folder=True), "embeddings_dir": OptionInfo(os.path.join(paths.models_path, 'embeddings'), "Folder with textual inversion embeddings", folder=True), "hypernetwork_dir": OptionInfo(os.path.join(paths.models_path, 'hypernetworks'), "Folder with Hypernetwork models", folder=True), @@ -464,7 +422,7 @@ options_templates.update(options_section(('saving-paths', "Image Naming & Paths" options_templates.update(options_section(('ui', "User Interface"), { "motd": OptionInfo(True, "Show MOTD"), - "gradio_theme": OptionInfo("black-teal", "UI theme", gr.Dropdown, lambda: {"choices": list_themes()}, refresh=refresh_themes), + "gradio_theme": OptionInfo("black-teal", "UI theme", gr.Dropdown, lambda: {"choices": theme.list_themes()}, refresh=theme.refresh_themes), "theme_style": OptionInfo("Auto", "Theme mode", gr.Radio, {"choices": ["Auto", "Dark", "Light"]}), "tooltips": OptionInfo("UI Tooltips", "UI tooltips", gr.Radio, {"choices": ["None", "Browser default", "UI tooltips"], "visible": False}), "compact_view": OptionInfo(False, "Compact view"), @@ -611,7 +569,6 @@ options_templates.update(options_section(('extra_networks', "Extra Networks"), { "lora_preferred_name": OptionInfo("filename", "LoRA preffered name", gr.Radio, {"choices": ["filename", "alias"]}), "lora_add_hashes_to_infotext": OptionInfo(True, "LoRA add hash info"), "lora_in_memory_limit": OptionInfo(0, "LoRA memory cache", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), - "lyco_patch_lora": OptionInfo(False, "Use LyCoris handler for all LoRA types", gr.Checkbox, { "visible": False }), "lora_functional": OptionInfo(False, "Use Kohya method for handling multiple LoRA", gr.Checkbox, { "visible": False }), "sd_hypernetwork": OptionInfo("None", "Add hypernetwork to prompt", gr.Dropdown, { "choices": ["None"], "visible": False }), @@ -809,45 +766,6 @@ if devices.backend == "directml": directml_do_hijack() -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: - request = urllib.request.Request("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono", method="HEAD") - res = urllib.request.urlopen(request, timeout=3.0).status # pylint: disable=consider-using-with - except Exception: - 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 in list_builtin_themes(): - gradio_theme = gr.themes.Base(**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 Exception: - 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: # compatibility with previous global-tqdm # import tqdm def __init__(self): diff --git a/modules/styles.py b/modules/styles.py index 2b637800a..ea7c2dc7b 100644 --- a/modules/styles.py +++ b/modules/styles.py @@ -124,7 +124,7 @@ class StyleDatabase: list_folder(self.path) self.styles = dict(sorted(self.styles.items(), key=lambda style: style[1].filename)) if self.built_in: - self.load_style(os.path.join(paths.data_path, 'html', 'art-styles.json'), 'built-in') + self.load_style(os.path.join('html', 'art-styles.json'), 'built-in') log.debug(f'Loaded styles: folder={self.path} items={len(self.styles.keys())}') diff --git a/modules/theme.py b/modules/theme.py new file mode 100644 index 000000000..293a174fb --- /dev/null +++ b/modules/theme.py @@ -0,0 +1,88 @@ +import os +import json +import urllib.request +import gradio as gr +import modules.shared +# from modules.shared import log, opts, req, writefile + + +gradio_theme = gr.themes.Base() + + +def list_builtin_themes(): + files = [os.path.splitext(f)[0] for f in os.listdir('javascript') if f.endswith('.css')] + return files + + +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 = list_builtin_themes() + default = ["gradio/default", "gradio/base", "gradio/glass", "gradio/monochrome", "gradio/soft"] + external = {x['id'] for x in res if x['status'] == 'RUNNING' and 'test' not in x['id'].lower()} + modules.shared.log.debug(f'Themes: builtin={len(builtin)} default={len(default)} external={len(external)}') + themes = sorted(builtin) + sorted(default) + sorted(external, key=str.casefold) + return themes + + +def refresh_themes(): + try: + r = modules.shared.req('https://huggingface.co/datasets/freddyaboulton/gradio-theme-subdomains/resolve/main/subdomains.json') + if r.status_code == 200: + res = r.json() + fn = os.path.join('html', 'themes.json') + modules.shared.writefile(res, fn) + list_themes() + else: + modules.shared.log.error('Error refreshing UI themes') + except Exception: + modules.shared.log.error('Exception refreshing UI themes') + + +def reload_gradio_theme(theme_name=None): + global gradio_theme # pylint: disable=global-statement + if not theme_name: + theme_name = modules.shared.opts.gradio_theme + default_font_params = {} + res = 0 + try: + request = urllib.request.Request("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono", method="HEAD") + res = urllib.request.urlopen(request, timeout=3.0).status # pylint: disable=consider-using-with + except Exception: + res = 0 + if res != 200: + modules.shared.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'] + } + is_builtin = theme_name in list_builtin_themes() + modules.shared.log.info(f'Loading UI theme: name={theme_name} style={modules.shared.opts.theme_style} base={"style.css" if is_builtin else "base.css"}') + if is_builtin: + gradio_theme = gr.themes.Base(**default_font_params) + elif theme_name.startswith("gradio/"): + modules.shared.log.warning('Using Gradio default theme which is not optimized for SD.Next') + 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: + modules.shared.log.warning('Using 3rd party theme which is not optimized for SD.Next') + gradio_theme = gr.themes.ThemeClass.from_hub(theme_name) + except Exception: + modules.shared.log.error("Theme download error accessing HuggingFace") + gradio_theme = gr.themes.Default(**default_font_params) + return is_builtin diff --git a/modules/ui.py b/modules/ui.py index 5a7573be8..b7edcdaac 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -25,6 +25,7 @@ import modules.shared import modules.errors import modules.styles import modules.extras +import modules.theme import modules.textual_inversion.ui import modules.sd_samplers @@ -250,7 +251,7 @@ def create_toprow(is_img2img): pause = gr.Button('Pause', elem_id=f"{id_part}_pause") pause.click(fn=lambda: modules.shared.state.pause(), _js='checkPaused', inputs=[], outputs=[]) with gr.Row(elem_id=f"{id_part}_tools"): - button_paste = gr.Button(value='Apply', variant='secondary', elem_id="paste") # symbols.paste + button_paste = gr.Button(value='Restore', variant='secondary', elem_id="paste") # symbols.paste button_clear = gr.Button(value='Clear', variant='secondary', elem_id=f"{id_part}_clear_prompt_btn") # symbols.clear button_extra = gr.Button(value='Networks', variant='secondary', elem_id=f"{id_part}_extra_networks_btn") # symbols.networks button_clear.click(fn=lambda *x: ['', ''], inputs=[prompt, negative_prompt], outputs=[prompt, negative_prompt], show_progress=False) @@ -1103,7 +1104,7 @@ def create_ui(startup_timer = None): for _interface, label, _ifid in interfaces: modules.shared.tab_names.append(label) - with gr.Blocks(theme=modules.shared.gradio_theme, analytics_enabled=False, title="SD.Next") as demo: + with gr.Blocks(theme=modules.theme.gradio_theme, analytics_enabled=False, title="SD.Next") as demo: with gr.Row(elem_id="quicksettings", variant="compact"): for _i, k, _item in sorted(quicksettings_list, key=lambda x: quicksettings_names.get(x[1], x[0])): component = create_setting_component(k, is_quicksettings=True) @@ -1226,19 +1227,20 @@ def html_body(): return body -def html_css(): +def html_css(is_builtin: bool): added = [] def stylesheet(fn): added.append(fn) return f'' - head = stylesheet(os.path.join(script_path, 'javascript/style.css')) + css = 'sdnext.css' if is_builtin else 'base.css' + head = stylesheet(os.path.join(script_path, 'javascript', css)) for cssfile in modules.scripts.list_files_with_name("style.css"): if not os.path.isfile(cssfile): continue head += stylesheet(cssfile) - if opts.gradio_theme in modules.shared.list_builtin_themes(): + if opts.gradio_theme in modules.theme.list_builtin_themes(): head += stylesheet(os.path.join(script_path, "javascript", f"{opts.gradio_theme}.css")) if os.path.exists(os.path.join(data_path, "user.css")): head += stylesheet(os.path.join(data_path, "user.css")) @@ -1248,8 +1250,9 @@ def html_css(): def reload_javascript(): + is_builtin = modules.theme.reload_gradio_theme() head = html_head() - css = html_css() + css = html_css(is_builtin) body = html_body() def template_response(*args, **kwargs): diff --git a/requirements.txt b/requirements.txt index 7d5e41aef..0f716d03e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -43,7 +43,7 @@ fasteners dctorch compel==2.0.2 torchsde==0.2.6 -typing-extensions==4.7.1 +typing-extensions==4.8.0 antlr4-python3-runtime==4.9.3 requests==2.31.0 tqdm==4.66.1 @@ -64,4 +64,4 @@ tomesd==0.1.3 urllib3==1.26.15 Pillow==9.5.0 timm==0.9.7 -pydantic==1.10.11 +pydantic==1.10.13 diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 3a251e668..81ef716d1 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -225,27 +225,32 @@ axis_options = [ AxisOption("Clip skip", int, apply_clip_skip), AxisOption("Denoising strength", float, apply_field("denoising_strength")), AxisOption("Prompt order", str_permutations, apply_order, fmt=format_value_join_list), - AxisOption("Upscaler", str, apply_upscaler, choices=lambda: [x.name for x in shared.sd_upscalers][1:]), - AxisOption("Face restore", str, apply_face_restore, fmt=format_value), - AxisOption("Token merging ratio (txt2img)", float, apply_override('token_merging_ratio')), - AxisOption("Token merging ratio (hires)", float, apply_override('token_merging_ratio_hr')), + AxisOption("[Postprocess] Upscaler", str, apply_upscaler, choices=lambda: [x.name for x in shared.sd_upscalers][1:]), + AxisOption("[Postprocess] Face restore", str, apply_face_restore, fmt=format_value), AxisOptionImg2Img("Image mask weight", float, apply_field("inpainting_mask_weight")), AxisOption("Model dictionary", str, apply_dict, fmt=format_value, cost=1.0, choices=lambda: ['None'] + list(sd_models.checkpoints_list)), - AxisOption("Sampler sigma min", float, apply_field("s_min")), - AxisOption("Sampler sigma max", float, apply_field("s_max")), - AxisOption("Sampler sigma tmin", float, apply_field("s_tmin")), - AxisOption("Sampler sigma tmax", float, apply_field("s_tmax")), - AxisOption("Sampler sigma Churn", float, apply_field("s_churn")), - AxisOption("Sampler sigma noise", float, apply_field("s_noise")), - AxisOption("Sampler eta", float, apply_field("eta")), - AxisOption("Sampler solver order", int, apply_setting("schedulers_solver_order")), - AxisOption("SecondPass upscaler", str, apply_field("hr_upscaler"), choices=lambda: [*shared.latent_upscale_modes, *[x.name for x in shared.sd_upscalers]]), - AxisOption("SecondPass sampler", str, apply_latent_sampler, fmt=format_value, confirm=confirm_samplers, choices=lambda: [x.name for x in sd_samplers.samplers]), - AxisOption("SecondPass denoising Strength", float, apply_field("denoising_strength")), - AxisOption("SecondPass steps", int, apply_field("hr_second_pass_steps")), - AxisOption("SecondPass CFG scale", float, apply_field("image_cfg_scale")), - AxisOption("SecondPass guidance rescale", float, apply_field("diffusers_guidance_rescale")), - AxisOption("SecondPass refiner start", float, apply_field("refiner_start")), + AxisOption("[Sampler] sigma min", float, apply_field("s_min")), + AxisOption("[Sampler] sigma max", float, apply_field("s_max")), + AxisOption("[Sampler] sigma tmin", float, apply_field("s_tmin")), + AxisOption("[Sampler] sigma tmax", float, apply_field("s_tmax")), + AxisOption("[Sampler] sigma Churn", float, apply_field("s_churn")), + AxisOption("[Sampler] sigma noise", float, apply_field("s_noise")), + AxisOption("[Sampler] eta", float, apply_field("eta")), + AxisOption("[Sampler] solver order", int, apply_setting("schedulers_solver_order")), + AxisOption("[Second pass] upscaler", str, apply_field("hr_upscaler"), choices=lambda: [*shared.latent_upscale_modes, *[x.name for x in shared.sd_upscalers]]), + AxisOption("[Second pass] sampler", str, apply_latent_sampler, fmt=format_value, confirm=confirm_samplers, choices=lambda: [x.name for x in sd_samplers.samplers]), + AxisOption("[Second pass] denoising Strength", float, apply_field("denoising_strength")), + AxisOption("[Second pass] steps", int, apply_field("hr_second_pass_steps")), + AxisOption("[Second pass] CFG scale", float, apply_field("image_cfg_scale")), + AxisOption("[Second pass] guidance rescale", float, apply_field("diffusers_guidance_rescale")), + AxisOption("[Second pass] refiner start", float, apply_field("refiner_start")), + AxisOption("[Second pass] refiner start", float, apply_field("refiner_start")), + AxisOption("[TOME] Token merging ratio (txt2img)", float, apply_override('token_merging_ratio')), + AxisOption("[TOME] Token merging ratio (hires)", float, apply_override('token_merging_ratio_hr')), + AxisOption("[FreeU] 1st stage backbone factor", float, apply_setting('freeu_b1')), + AxisOption("[FreeU] 2nd stage backbone factor", float, apply_setting('freeu_b2')), + AxisOption("[FreeU] 1st stage skip factor", float, apply_setting('freeu_s1')), + AxisOption("[FreeU] 2nd stage skip factor", float, apply_setting('freeu_s2')), ] diff --git a/webui.py b/webui.py index fafa3aca8..1974cd89e 100644 --- a/webui.py +++ b/webui.py @@ -115,7 +115,7 @@ def initialize(): shared.opts.onchange("sd_vae", wrap_queued_call(lambda: modules.sd_vae.reload_vae_weights()), call=False) shared.opts.onchange("temp_dir", ui_tempdir.on_tmpdir_changed) - shared.opts.onchange("gradio_theme", shared.reload_gradio_theme) + # shared.opts.onchange("gradio_theme", shared.reload_gradio_theme) timer.startup.record("onchange") modules.textual_inversion.textual_inversion.list_textual_inversion_templates() diff --git a/wiki b/wiki index 128faf8d7..3b4fb16cb 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 128faf8d7fc9e8f94376fb211b0e44f30d15638f +Subproject commit 3b4fb16cb127069f76dc2ae049603f8a951864d8