From bf8a044f7b46c3a665a3feac54e68b1d2164e8b7 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 30 Mar 2024 11:44:39 -0400 Subject: [PATCH] add theme type --- CHANGELOG.md | 6 +- modules/shared.py | 58 ++++++++++++---- modules/theme.py | 141 +++++++++++++++++++++++---------------- modules/ui_javascript.py | 47 +++++++------ wiki | 2 +- 5 files changed, 156 insertions(+), 98 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1c73826c..d5037a0e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ - Control API scripts compatibility -## Update for 2024-03-29 +## Update for 2024-03-30 - **Features**: - **Gallery**: list, preview, search through all your images and videos! @@ -18,6 +18,10 @@ *settings*: optional additional user-defined folders, thumbnails in fixed or variable aspect-ratio - **OneDiff**: new optimization/compile engine, thanks @aifartist as with all other compile engines, enable via *settings -> compute settings -> compile* +- **UI**: + - Theme types: **Standard*, **Modern** *(experimental)*, **None** (used for Gradio and Huggingface 3rd party themes) + Specifying a theme type updates list of available themes + - See details in [WiKi](https://github.com/vladmandic/automatic/wiki/Themes) - **Changes**: - Removed built-in extensions: *ControlNet* and *Image-Browser* as both *image-browser* and *controlnet* have native equivalents diff --git a/modules/shared.py b/modules/shared.py index 7ec1bf023..0566cb62f 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -288,30 +288,59 @@ def list_samplers(): def temp_disable_extensions(): disable_safe = ['sd-webui-controlnet', 'multidiffusion-upscaler-for-automatic1111', 'a1111-sd-webui-lycoris', 'sd-webui-agent-scheduler', 'clip-interrogator-ext', 'stable-diffusion-webui-rembg', 'sd-extension-chainner', 'stable-diffusion-webui-images-browser'] disable_diffusers = ['sd-webui-controlnet', 'multidiffusion-upscaler-for-automatic1111', 'a1111-sd-webui-lycoris', 'sd-webui-animatediff'] - disable_themes = [] + disable_themes = ['sd-webui-lobe-theme', 'cozy-nest', 'sdnext-ui-ux'] disable_original = [] disabled = [] - theme_name = modules.shared.cmd_opts.theme or modules.shared.opts.gradio_theme - if theme_name.lower() != 'modern' and not theme_name.lower().startswith('modern/'): - disable_themes.append('sdnext-ui-ux') - if theme_name.lower() != 'lobe': - disable_themes.append('sd-webui-lobe-theme') - if theme_name.lower() != 'cozy-nest': - disable_themes.append('Cozy-Nest') + if modules.shared.cmd_opts.theme is not None: + theme_name = modules.shared.cmd_opts.theme.lower() + else: + theme_name = f'{modules.shared.opts.theme_type}/{modules.shared.opts.gradio_theme}'.lower() + + if theme_name == 'lobe': + disable_themes.remove('sd-webui-lobe-theme') + elif theme_name == 'cozy-nest' or theme_name == 'cozy': + disable_themes.remove('cozy-nest') + elif '/' not in theme_name: # set default themes per type + if theme_name == 'standard' or theme_name == 'default': + theme_name = 'standard/black-teal' + if theme_name == 'modern': + theme_name = 'modern/sdxl_alpha' + if theme_name == 'gradio': + theme_name = 'gradio/default' + if theme_name == 'huggingface': + theme_name = 'huggingface/blaaa' + + if theme_name.startswith('standard'): + modules.shared.opts.data['theme_type'] = 'Standard' + modules.shared.opts.data['gradio_theme'] = theme_name[9:] + elif theme_name.startswith('modern'): + modules.shared.opts.data['theme_type'] = 'Modern' + modules.shared.opts.data['gradio_theme'] = theme_name[7:] + disable_themes.remove('sdnext-ui-ux') + elif theme_name.startswith('gradio'): + modules.shared.opts.data['theme_type'] = 'None' + modules.shared.opts.data['gradio_theme'] = theme_name + elif theme_name.startswith('huggingface'): + modules.shared.opts.data['theme_type'] = 'None' + modules.shared.opts.data['gradio_theme'] = theme_name + else: + modules.shared.opts.data['theme_type'] = 'None' + modules.shared.opts.data['gradio_theme'] = theme_name + for ext in disable_themes: - if ext not in opts.disabled_extensions: + if ext.lower() not in opts.disabled_extensions: disabled.append(ext) if cmd_opts.safe: for ext in disable_safe: - if ext not in opts.disabled_extensions: + if ext.lower() not in opts.disabled_extensions: disabled.append(ext) if backend == Backend.DIFFUSERS: for ext in disable_diffusers: - if ext not in opts.disabled_extensions: + if ext.lower() not in opts.disabled_extensions: disabled.append(ext) if backend == Backend.ORIGINAL: for ext in disable_original: - if ext not in opts.disabled_extensions: + if ext.lower() not in opts.disabled_extensions: disabled.append(ext) cmd_opts.controlnet_loglevel = 'WARNING' return disabled @@ -582,12 +611,13 @@ options_templates.update(options_section(('saving-paths', "Image Naming & Paths" })) options_templates.update(options_section(('ui', "User Interface Options"), { - "motd": OptionInfo(True, "Show MOTD"), - "gradio_theme": OptionInfo("black-teal", "UI theme", gr.Dropdown, lambda: {"choices": theme.list_themes()}, refresh=theme.refresh_themes), + "theme_type": OptionInfo("Standard", "Theme type", gr.Radio, {"choices": ["Modern", "Standard", "None"]}), "theme_style": OptionInfo("Auto", "Theme mode", gr.Radio, {"choices": ["Auto", "Dark", "Light"]}), + "gradio_theme": OptionInfo("black-teal", "UI theme", gr.Dropdown, lambda: {"choices": theme.list_themes()}, refresh=theme.refresh_themes), "font_size": OptionInfo(14, "Font size", gr.Slider, {"minimum": 8, "maximum": 32, "step": 1, "visible": True}), "tooltips": OptionInfo("UI Tooltips", "UI tooltips", gr.Radio, {"choices": ["None", "Browser default", "UI tooltips"], "visible": False}), "aspect_ratios": OptionInfo("1:1, 4:3, 16:9, 16:10, 21:9, 3:4, 9:16, 10:16, 9:21", "Allowed aspect ratios"), + "motd": OptionInfo(True, "Show MOTD"), "compact_view": OptionInfo(False, "Compact view"), "return_grid": OptionInfo(True, "Show grid in results"), "return_mask": OptionInfo(False, "Inpainting include greyscale mask in results"), diff --git a/modules/theme.py b/modules/theme.py index 04fd3dda4..5921ce957 100644 --- a/modules/theme.py +++ b/modules/theme.py @@ -37,77 +37,102 @@ def refresh_themes(no_update=False): def list_themes(): - builtin = list_builtin_themes() extensions = [e.name for e in modules.extensions.extensions if e.enabled] - engines = [] - if 'sdnext-ui-ux' in extensions: + if 'sd-webui-lobe-theme' in extensions and modules.shared.opts.gradio_theme == 'lobe': + themes = ['lobe'] + modules.shared.opts.data['gradio_theme'] = themes[0] + modules.shared.opts.data['theme_type'] = 'None' + modules.shared.log.info('UI theme: extension="lobe"') + elif 'Cozy-Nest' in extensions and modules.shared.opts.gradio_theme == 'cozy-nest': + themes = ['cozy-nest'] + modules.shared.opts.data['gradio_theme'] = themes[0] + modules.shared.opts.data['theme_type'] = 'None' + modules.shared.log.info('UI theme: extension="cozy-nest"') + elif modules.shared.opts.theme_type == 'None': + gradio = ["gradio/default", "gradio/base", "gradio/glass", "gradio/monochrome", "gradio/soft"] + huggingface = refresh_themes(no_update=True) + huggingface = {x['id'] for x in huggingface if x['status'] == 'RUNNING' and 'test' not in x['id'].lower()} + huggingface = [f'huggingface/{x}' for x in huggingface] + themes = sorted(gradio) + sorted(huggingface, key=str.casefold) + modules.shared.log.debug(f'UI themes available: type=={modules.shared.opts.theme_type} gradio={len(gradio)} huggingface={len(huggingface)}') + elif modules.shared.opts.theme_type == 'Standard': + builtin = list_builtin_themes() + themes = sorted(builtin) + modules.shared.log.debug(f'UI themes available: type={modules.shared.opts.theme_type} themes={len(builtin)}') + elif modules.shared.opts.theme_type == 'Modern': ext = next((e for e in modules.extensions.extensions if e.name == 'sdnext-ui-ux'), None) folder = os.path.join(ext.path, 'themes') + themes = [] if os.path.exists(folder): for f in os.listdir(folder): if f.endswith('.css'): - engines.append(f'modern/{os.path.splitext(f)[0]}') - if len(engines) == 0: - engines.append('modern/sdxl_alpha') - if 'sd-webui-lobe-theme' in extensions: - modules.shared.log.info('Theme: installed="lobe"') - engines.append('lobe') - if 'Cozy-Nest' in extensions: - modules.shared.log.info('Theme: installed="cozy-nest"') - engines.append('cozy-nest') - gradio = ["gradio/default", "gradio/base", "gradio/glass", "gradio/monochrome", "gradio/soft"] - huggingface = refresh_themes(no_update=True) - huggingface = {x['id'] for x in huggingface if x['status'] == 'RUNNING' and 'test' not in x['id'].lower()} - huggingface = [f'huggingface/{x}' for x in huggingface] - modules.shared.log.debug(f'Themes: builtin={len(builtin)} gradio={len(gradio)} huggingface={len(huggingface)}') - themes = sorted(engines) + sorted(builtin) + sorted(gradio) + sorted(huggingface, key=str.casefold) + themes.append(os.path.splitext(f)[0]) + if len(themes) == 0: + themes.append('modern/sdxl_alpha') + themes = sorted(themes) + modules.shared.log.debug(f'UI themes available: type={modules.shared.opts.theme_type} themes={len(themes)}') + else: + modules.shared.log.error(f'UI themes: type={modules.shared.opts.theme_type} unknown') + themes = [] return themes -def reload_gradio_theme(theme_name=None): +def reload_gradio_theme(): global gradio_theme # pylint: disable=global-statement - theme_name = theme_name or modules.shared.cmd_opts.theme or modules.shared.opts.gradio_theme - if theme_name == 'default': - theme_name = 'black-teal' - if theme_name == 'modern': - theme_name = 'modern/sdxl_alpha' - modules.shared.opts.data['gradio_theme'] = theme_name + theme_name = modules.shared.opts.gradio_theme default_font_params = { 'font':['Helvetica', 'ui-sans-serif', 'system-ui', 'sans-serif'], 'font_mono':['IBM Plex Mono', 'ui-monospace', 'Consolas', 'monospace'] } - base = 'base.css' - if theme_name.lower() in list_builtin_themes(): - base = 'sdnext.css' + gradio_theme = gr.themes.Base(**default_font_params) + + available_themes = list_themes() + if theme_name not in available_themes: + modules.shared.log.error(f'UI theme invalid: type={modules.shared.opts.theme_type} theme="{theme_name}" available={available_themes}') + if modules.shared.opts.theme_type == 'Standard': + theme_name = 'black-teal' + elif modules.shared.opts.theme_type == 'Modern': + theme_name = 'sdxl_alpha' + + modules.shared.opts.data['gradio_theme'] = theme_name + + if theme_name.lower() in ['lobe', 'cozy-nest']: + modules.shared.log.info(f'UI theme extension: name="{theme_name}" style={modules.shared.opts.theme_style}') + return None + elif modules.shared.opts.theme_type == 'Standard': gradio_theme = gr.themes.Base(**default_font_params) - modules.shared.log.info(f'UI theme: name="{theme_name}" style={modules.shared.opts.theme_style} base={base}') - return True - elif theme_name.lower() in ['lobe', 'cozy-nest']: + modules.shared.log.info(f'UI theme: type={modules.shared.opts.theme_type} name="{theme_name}" style={modules.shared.opts.theme_style}') + return 'sdnext.css' + elif modules.shared.opts.theme_type == 'Modern': gradio_theme = gr.themes.Base(**default_font_params) - modules.shared.log.info(f'UI theme: name="{theme_name}" style={modules.shared.opts.theme_style} base={base}') - elif theme_name.lower() == 'modern' or theme_name.lower().startswith('modern/'): - gradio_theme = gr.themes.Base(**default_font_params) - modules.shared.log.info(f'UI theme: name="{theme_name}" style={modules.shared.opts.theme_style} base={base}') - elif theme_name.startswith("gradio/"): - modules.shared.log.info(f'UI theme: name="{theme_name}" style={modules.shared.opts.theme_style} base={base}') - modules.shared.log.warning('UI theme: 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: - modules.shared.log.info(f'UI theme: name="{theme_name}" style={modules.shared.opts.theme_style} base={base}') - try: - hf_theme_name = theme_name.replace('huggingface/', '') - modules.shared.log.warning('UI Theme: using 3rd party theme which is not optimized for SD.Next') - gradio_theme = gr.themes.ThemeClass.from_hub(hf_theme_name) - except Exception as e: - modules.shared.log.error(f"UI theme: download error accessing HuggingFace {e}") - gradio_theme = gr.themes.Default(**default_font_params) - return False + modules.shared.log.info(f'UI theme: type={modules.shared.opts.theme_type} name="{theme_name}" style={modules.shared.opts.theme_style}') + return 'base.css' + elif modules.shared.opts.theme_type == 'None': + if theme_name.startswith('gradio/'): + modules.shared.log.warning('UI theme: using Gradio default theme which is not optimized for SD.Next') + if theme_name == "gradio/default": + gradio_theme = gr.themes.Default(**default_font_params) + elif theme_name == "gradio/base": + gradio_theme = gr.themes.Base(**default_font_params) + elif theme_name == "gradio/glass": + gradio_theme = gr.themes.Glass(**default_font_params) + elif theme_name == "gradio/monochrome": + gradio_theme = gr.themes.Monochrome(**default_font_params) + elif theme_name == "gradio/soft": + gradio_theme = gr.themes.Soft(**default_font_params) + else: + modules.shared.log.warning('UI theme: unknown Gradio theme') + theme_name = "gradio/default" + gradio_theme = gr.themes.Default(**default_font_params) + elif theme_name.startswith('huggingface/'): + modules.shared.log.warning('UI theme: using 3rd party theme which is not optimized for SD.Next') + try: + hf_theme_name = theme_name.replace('huggingface/', '') + gradio_theme = gr.themes.ThemeClass.from_hub(hf_theme_name) + except Exception as e: + modules.shared.log.error(f"UI theme: download error accessing HuggingFace {e}") + gradio_theme = gr.themes.Default(**default_font_params) + modules.shared.log.info(f'UI theme: type={modules.shared.opts.theme_type} name="{theme_name}" style={modules.shared.opts.theme_style}') + return 'base.css' + modules.shared.log.error(f'UI theme: type={modules.shared.opts.theme_type} unknown') + return None diff --git a/modules/ui_javascript.py b/modules/ui_javascript.py index c8edb20c7..9e0f93661 100644 --- a/modules/ui_javascript.py +++ b/modules/ui_javascript.py @@ -43,44 +43,43 @@ def html_body(): return body -def html_css(is_builtin: bool): - added = [] - +def html_css(css: str): def stylesheet(fn): - added.append(fn) return f'' - css = 'sdnext.css' if is_builtin else 'base.css' - head = stylesheet(os.path.join(script_path, 'javascript', css)) + head = '' + if css is not None: + 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) - theme_name = modules.shared.cmd_opts.theme or modules.shared.opts.gradio_theme or '' - if theme_name == 'default': - theme_name = 'black-teal' - if theme_name == 'modern' or theme_name == 'modern/default': - theme_name = 'modern/sdxl_alpha' - if theme_name.startswith('modern/'): - theme_name = theme_name[7:] + usercss = os.path.join(data_path, "user.css") if os.path.exists(os.path.join(data_path, "user.css")) else None + if modules.shared.opts.theme_type == 'Standard': + themecss = os.path.join(script_path, "javascript", f"{modules.shared.opts.gradio_theme}.css") + if os.path.exists(themecss): + head += stylesheet(themecss) + modules.shared.log.debug(f'UI theme: css="{themecss}" base="{css}" user="{usercss}"') + else: + modules.shared.log.error(f'UI theme: css="{themecss}" not found') + elif modules.shared.opts.theme_type == 'Modern': theme_folder = next((e.path for e in modules.extensions.extensions if e.name == 'sdnext-ui-ux'), None) - theme_file = os.path.join(theme_folder or '', 'themes', f'{theme_name}.css') - if os.path.exists(theme_file): - head += stylesheet(theme_file) - elif theme_name in theme.list_builtin_themes() and os.path.exists(os.path.join(script_path, "javascript", f"{theme_name}.css")): - head += stylesheet(os.path.join(script_path, "javascript", f"{theme_name}.css")) - if os.path.exists(os.path.join(data_path, "user.css")): - head += stylesheet(os.path.join(data_path, "user.css")) - added = [a.replace(script_path, '').replace('\\', '/') for a in added] - # log.debug(f'Adding CSS stylesheets: {added}') + themecss = os.path.join(theme_folder or '', 'themes', f'{modules.shared.opts.gradio_theme}.css') + if os.path.exists(themecss): + head += stylesheet(themecss) + modules.shared.log.debug(f'UI theme: css="{themecss}" base="{css}" user="{usercss}"') + else: + modules.shared.log.error(f'UI theme: css="{themecss}" not found') + if usercss is not None: + head += stylesheet(usercss) return head def reload_javascript(): - is_builtin = theme.reload_gradio_theme() + base_css = theme.reload_gradio_theme() head = html_head() - css = html_css(is_builtin) + css = html_css(base_css) body = html_body() def template_response(*args, **kwargs): diff --git a/wiki b/wiki index 108b18396..6acb597b1 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 108b183962e485d39fea47e65a1ccd3092cad7b6 +Subproject commit 6acb597b128c766592fc23b8078c9e142503743e