From 4d3851ecfd1e3d09d12a25817ebb41fbf45aabee Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 7 Mar 2024 11:57:09 -0500 Subject: [PATCH] styles editor --- CHANGELOG.md | 4 +- javascript/extraNetworks.js | 4 +- javascript/sdnext.css | 2 + modules/styles.py | 6 +- modules/ui_extra_networks.py | 116 +++++++++++++++++++++------- modules/ui_extra_networks_styles.py | 11 ++- 6 files changed, 105 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18d2c6945..d6e86a060 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ - EDM samplers for Playground 2.5 - Stable Cascade -## Update for 2024-03-06 +## Update for 2024-03-07 - [Playground v2.5](https://huggingface.co/playgroundai/playground-v2.5-1024px-aesthetic) - new model version from Playground: based on SDXL, but with some cool new concepts @@ -48,6 +48,8 @@ - *DPM++ 2M EDM* and *Euler EDM* EDM is a new solver algorithm currently available for DPM++2M and Euler samplers Note that using EDM samplers with non-EDM optimized models will provide just noise and vice-versa +- **Styles** + - new styles editor: networs -> styles -> edit - **UI** - *aspect-ratio** add selector and lock to width/height control allowed aspect ration can be configured via *settings -> user interface* diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index a8ba3bcf5..35918b44e 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -309,7 +309,7 @@ function quickSaveStyle() { } let enDirty = false; -function closeDetailsEN(args) { +function closeDetailsEN(...args) { // log('closeDetailsEN'); enDirty = true; const tabname = getENActiveTab(); @@ -317,7 +317,7 @@ function closeDetailsEN(args) { if (btnClose) setTimeout(() => btnClose.click(), 100); const btnRefresh = gradioApp().getElementById(`${tabname}_extra_refresh`); if (btnRefresh && enDirty) setTimeout(() => btnRefresh.click(), 100); - return args; + return [...args]; } function refeshDetailsEN(args) { diff --git a/javascript/sdnext.css b/javascript/sdnext.css index b88336b79..f94f7281e 100644 --- a/javascript/sdnext.css +++ b/javascript/sdnext.css @@ -228,6 +228,8 @@ table.settings-value-table td { padding: 0.4em; border: 1px solid #ccc; max-widt .extra-network-cards .card-list { display: flex; margin: 0.3em; padding: 0.3em; background: var(--input-background-fill); cursor: pointer; border-radius: var(--button-large-radius); } .extra-network-cards .card-list .tag { color: var(--primary-500); margin-left: 0.8em; } .extra-details-close { position: fixed; top: 0.2em; right: 0.2em; z-index: 99; background: var(--button-secondary-background-fill) !important; } +.extra-details-tabs textarea, .extra-details-tabs .gradio-json { overflow-y: scroll !important; scrollbar-width: unset !important; max-height: 15vh; } + #txt2img_description, #img2img_description, #control_description { max-height: 63px; overflow-y: auto !important; } #txt2img_description>label>textarea, #img2img_description>label>textarea, #control_description>label>textarea { font-size: var(--text-xs); height: 6em; } diff --git a/modules/styles.py b/modules/styles.py index 53b47f079..da5133216 100644 --- a/modules/styles.py +++ b/modules/styles.py @@ -10,12 +10,13 @@ from modules import files_cache class Style(): - def __init__(self, name: str, desc: str = "", prompt: str = "", negative_prompt: str = "", extra: str = "", filename: str = "", preview: str = "", mtime: float = 0): + def __init__(self, name: str, desc: str = "", prompt: str = "", negative_prompt: str = "", extra: str = "", wildcards: str = "", filename: str = "", preview: str = "", mtime: float = 0): self.name = name self.description = desc self.prompt = prompt self.negative_prompt = negative_prompt self.extra = extra + self.wildcards = wildcards self.filename = filename self.preview = preview self.mtime = mtime @@ -121,6 +122,7 @@ class StyleDatabase: prompt=style.get("prompt", ""), negative_prompt=style.get("negative", ""), extra=style.get("extra", ""), + wildcards=style.get("wildcards", ""), preview=style.get("preview", None), filename=fn, mtime=os.path.getmtime(fn), @@ -227,7 +229,7 @@ class StyleDatabase: name = row["name"] prompt = row["prompt"] if "prompt" in row else row["text"] negative = row.get("negative_prompt", "") if "negative_prompt" in row else row.get("negative", "") - self.styles[name] = Style(name, desc=name, prompt=prompt, negative_prompt=negative, extra="") + self.styles[name] = Style(name, desc=name, prompt=prompt, negative_prompt=negative) log.debug(f'Migrated style: {self.styles[name].__dict__}') num += 1 except Exception: diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 25f75c9f9..1a8da2dd2 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -447,10 +447,12 @@ def get_pages(title=None): class ExtraNetworksUi: def __init__(self): self.tabname: str = None - self.pages: list(str) = None + self.pages: list[str] = None self.visible: gr.State = None self.state: gr.Textbox = None self.details: gr.Group = None + self.details_tabs: gr.Group = None + self.details_text: gr.Group = None self.tabs: gr.Tabs = None self.gallery: gr.Gallery = None self.description: gr.Textbox = None @@ -533,26 +535,39 @@ def create_ui(container, button_parent, tabname, skip_indexing = False): with gr.Row(): btn_save_img = gr.Button('Replace', elem_classes=['small-button']) btn_delete_img = gr.Button('Delete', elem_classes=['small-button']) - with gr.Tabs(): - with gr.Tab('Description'): - desc = gr.Textbox('', show_label=False, lines=8, placeholder="Extra network description...") - ui.details_components.append(desc) - with gr.Row(): - btn_save_desc = gr.Button('Save', elem_classes=['small-button'], elem_id=f'{tabname}_extra_details_save_desc') - btn_delete_desc = gr.Button('Delete', elem_classes=['small-button'], elem_id=f'{tabname}_extra_details_delete_desc') - btn_close_desc = gr.Button('Close', elem_classes=['small-button'], elem_id=f'{tabname}_extra_details_close_desc') - btn_close_desc.click(fn=lambda: gr.update(visible=False), _js='refeshDetailsEN', inputs=[], outputs=[ui.details]) - with gr.Tab('Model metadata'): - info = gr.JSON({}, show_label=False) - ui.details_components.append(info) - with gr.Row(): - btn_save_info = gr.Button('Save', elem_classes=['small-button'], elem_id=f'{tabname}_extra_details_save_info') - btn_delete_info = gr.Button('Delete', elem_classes=['small-button'], elem_id=f'{tabname}_extra_details_delete_info') - btn_close_info = gr.Button('Close', elem_classes=['small-button'], elem_id=f'{tabname}_extra_details_close_info') - btn_close_info.click(fn=lambda: gr.update(visible=False), _js='refeshDetailsEN', inputs=[], outputs=[ui.details]) - with gr.Tab('Embedded metadata'): - meta = gr.JSON({}, show_label=False) - ui.details_components.append(meta) + with gr.Group(elem_id=f"{tabname}_extra_details_tabs", visible=False) as ui.details_tabs: + with gr.Tabs(): + with gr.Tab('Description', elem_classes=['extra-details-tabs']): + desc = gr.Textbox('', show_label=False, lines=8, placeholder="Extra network description...") + ui.details_components.append(desc) + with gr.Row(): + btn_save_desc = gr.Button('Save', elem_classes=['small-button'], elem_id=f'{tabname}_extra_details_save_desc') + btn_delete_desc = gr.Button('Delete', elem_classes=['small-button'], elem_id=f'{tabname}_extra_details_delete_desc') + btn_close_desc = gr.Button('Close', elem_classes=['small-button'], elem_id=f'{tabname}_extra_details_close_desc') + btn_close_desc.click(fn=lambda: gr.update(visible=False), _js='refeshDetailsEN', inputs=[], outputs=[ui.details]) + with gr.Tab('Model metadata', elem_classes=['extra-details-tabs']): + info = gr.JSON({}, show_label=False) + ui.details_components.append(info) + with gr.Row(): + btn_save_info = gr.Button('Save', elem_classes=['small-button'], elem_id=f'{tabname}_extra_details_save_info') + btn_delete_info = gr.Button('Delete', elem_classes=['small-button'], elem_id=f'{tabname}_extra_details_delete_info') + btn_close_info = gr.Button('Close', elem_classes=['small-button'], elem_id=f'{tabname}_extra_details_close_info') + btn_close_info.click(fn=lambda: gr.update(visible=False), _js='refeshDetailsEN', inputs=[], outputs=[ui.details]) + with gr.Tab('Embedded metadata', elem_classes=['extra-details-tabs']): + meta = gr.JSON({}, show_label=False) + ui.details_components.append(meta) + with gr.Group(elem_id=f"{tabname}_extra_details_text", visible=False) as ui.details_text: + description = gr.Textbox(label='Description', lines=1, placeholder="Style description...") + prompt = gr.Textbox(label='Prompt', lines=2, placeholder="Prompt...") + negative = gr.Textbox(label='Negative prompt', lines=2, placeholder="Negative prompt...") + extra = gr.Textbox(label='Parameters', lines=2, placeholder="Generation parameters overrides...") + wildcards = gr.Textbox(label='Wildcards', lines=2, placeholder="Wildcard prompt replacements...") + ui.details_components += [description, prompt, negative, extra, wildcards] + with gr.Row(): + btn_save_style = gr.Button('Save', elem_classes=['small-button'], elem_id=f'{tabname}_extra_details_save_style') + btn_delete_style = gr.Button('Delete', elem_classes=['small-button'], elem_id=f'{tabname}_extra_details_delete_style') + btn_close_style = gr.Button('Close', elem_classes=['small-button'], elem_id=f'{tabname}_extra_details_close_style') + btn_close_style.click(fn=lambda: gr.update(visible=False), _js='refeshDetailsEN', inputs=[], outputs=[ui.details]) with ui.tabs: def ui_tab_change(page): @@ -639,10 +654,7 @@ def create_ui(container, button_parent, tabname, skip_indexing = False): def fn_delete_desc(desc): if ui.last_item is None: return desc - if hasattr(ui.last_item, 'type') and ui.last_item.type == 'Style': - fn = os.path.splitext(ui.last_item.filename)[0] + '.json' - else: - fn = os.path.splitext(ui.last_item.filename)[0] + '.txt' + fn = os.path.splitext(ui.last_item.filename)[0] + '.txt' if os.path.exists(fn): shared.log.debug(f'Extra network delete desc: item={ui.last_item.name} filename="{fn}"') os.remove(fn) @@ -665,14 +677,39 @@ def create_ui(container, button_parent, tabname, skip_indexing = False): return '' return info + def fn_save_style(info, description, prompt, negative, extra, wildcards): + if not isinstance(info, dict) or isinstance(info, list): + shared.log.warning(f'Extra network save style skip: item={ui.last_item.name} not a dict: {type(info)}') + return info + if ui.last_item is None: + return info + fn = os.path.splitext(ui.last_item.filename)[0] + '.json' + if hasattr(ui.last_item, 'type') and ui.last_item.type == 'Style': + info.update(**{ 'description': description, 'prompt': prompt, 'negative': negative, 'extra': extra, 'wildcards': wildcards }) + shared.writefile(info, fn, silent=True) + shared.log.debug(f'Extra network save style: item={ui.last_item.name} filename="{fn}"') + return info + + def fn_delete_style(info): + if ui.last_item is None: + return info + fn = os.path.splitext(ui.last_item.filename)[0] + '.json' + if os.path.exists(fn): + shared.log.debug(f'Extra network delete style: item={ui.last_item.name} filename="{fn}"') + os.remove(fn) + return {} + return info + btn_save_img.click(fn=fn_save_img, _js='closeDetailsEN', inputs=[img], outputs=[img]) btn_delete_img.click(fn=fn_delete_img, _js='closeDetailsEN', inputs=[img], outputs=[img]) btn_save_desc.click(fn=fn_save_desc, _js='closeDetailsEN', inputs=[desc], outputs=[desc]) btn_delete_desc.click(fn=fn_delete_desc, _js='closeDetailsEN', inputs=[desc], outputs=[desc]) btn_save_info.click(fn=fn_save_info, _js='closeDetailsEN', inputs=[info], outputs=[info]) btn_delete_info.click(fn=fn_delete_info, _js='closeDetailsEN', inputs=[info], outputs=[info]) + btn_save_style.click(fn=fn_save_style, _js='closeDetailsEN', inputs=[info, description, prompt, negative, extra, wildcards], outputs=[info]) + btn_delete_style.click(fn=fn_delete_style, _js='closeDetailsEN', inputs=[info], outputs=[info]) - def show_details(text, img, desc, info, meta, params): + def show_details(text, img, desc, info, meta, description, prompt, negative, parameters, wildcards, params, _dummy1=None, _dummy2=None): page, item = get_item(state, params) if item is not None and hasattr(item, 'name'): stat = os.stat(item.filename) if os.path.exists(item.filename) else None @@ -730,12 +767,17 @@ def create_ui(container, button_parent, tabname, skip_indexing = False): Comment{meta.get('ss_training_comment', 'N/A')} ''' if page.title == 'Style': + description = item.description + prompt = item.prompt + negative = item.negative + parameters = item.extra + wildcards = item.wildcards style = f''' Name{item.name} Description{item.description} Preview Embedded{item.preview.startswith('data:')} ''' - desc = f'Name: {os.path.basename(item.name)}\nDescription: {item.description}\nPrompt: {item.prompt}\nNegative: {item.negative}\nExtra: {item.extra}\n' + # desc = f'Name: {os.path.basename(item.name)}\nDescription: {item.description}\nPrompt: {item.prompt}\nNegative: {item.negative}\nExtra: {item.extra}\n' text = f'''

{item.name}

@@ -752,7 +794,21 @@ def create_ui(container, button_parent, tabname, skip_indexing = False):
{note} ''' - return [text, img, desc, info, meta, gr.update(visible=item is not None)] + return [ + text, # gr.html + img, # gr.image + desc, # gr.textbox + info, # gr.json + meta, # gr.json + description, # gr.textbox + prompt, # gr.textbox + negative, # gr.textbox + parameters, # gr.textbox + wildcards, # gr.textbox + gr.update(visible=item is not None), # details ui visible + gr.update(visible=page is not None and page.title != 'Style'), # details ui tabs visible + gr.update(visible=page is not None and page.title == 'Style'), # details ui text visible + ] def ui_refresh_click(title): pages = [] @@ -796,7 +852,7 @@ def create_ui(container, button_parent, tabname, skip_indexing = False): else: prompt = '' params = generation_parameters_copypaste.parse_generation_parameters(prompt) - res = show_details(text=None, img=None, desc=None, info=None, meta=None, params=params) + res = show_details(text=None, img=None, desc=None, info=None, meta=None, parameters=None, description=None, prompt=None, negative=None, wildcards=None, params=params) return res def ui_quicksave_click(name): @@ -836,7 +892,7 @@ def create_ui(container, button_parent, tabname, skip_indexing = False): ui.button_scan.click(fn=ui_scan_click, _js='getENActivePage', inputs=[ui.search], outputs=ui.pages) ui.button_save.click(fn=ui_save_click, inputs=[], outputs=ui.details_components + [ui.details]) ui.button_quicksave.click(fn=ui_quicksave_click, _js="() => prompt('Prompt name', '')", inputs=[ui.search], outputs=[]) - ui.button_details.click(show_details, _js="getCardDetails", inputs=ui.details_components + [dummy], outputs=ui.details_components + [ui.details]) + ui.button_details.click(show_details, _js="getCardDetails", inputs=ui.details_components + [dummy, dummy, dummy], outputs=ui.details_components + [ui.details, ui.details_tabs, ui.details_text]) ui.state.change(state_change, inputs=[ui.state], outputs=[]) return ui diff --git a/modules/ui_extra_networks_styles.py b/modules/ui_extra_networks_styles.py index 859fba2cd..d8bb13cfa 100644 --- a/modules/ui_extra_networks_styles.py +++ b/modules/ui_extra_networks_styles.py @@ -13,7 +13,7 @@ class ExtraNetworksPageStyles(ui_extra_networks.ExtraNetworksPage): def parse_desc(self, desc): lines = desc.strip().split("\n") - params = { 'name': '', 'description': '', 'prompt': '', 'negative': '', 'extra': ''} + params = { 'name': '', 'description': '', 'prompt': '', 'negative': '', 'extra': '', 'wildcards': ''} found = '' for line in lines: line = line.strip() @@ -32,6 +32,9 @@ class ExtraNetworksPageStyles(ui_extra_networks.ExtraNetworksPage): elif line.lower().startswith('extra:'): found = 'extra' params['extra'] = line[6:].strip() + elif line.lower().startswith('wildcards:'): + found = 'wildcards' + params['wildcards'] = line[10:].strip() elif found != '': params[found] += '\n' + line if params['name'] == '': @@ -53,10 +56,11 @@ class ExtraNetworksPageStyles(ui_extra_networks.ExtraNetworksPage): "title": name, "filename": fn, "preview": self.find_preview(name), - "description": '', + "description": params.get('Description', ''), "prompt": params.get('Prompt', ''), "negative": params.get('Negative prompt', ''), - "extra": '', + "extra": params.get('Extra', ''), + "wildcards": params.get('Wildcards', ''), "local_preview": f"{name}.{shared.opts.samples_format}", } return item @@ -82,6 +86,7 @@ class ExtraNetworksPageStyles(ui_extra_networks.ExtraNetworksPage): "prompt": getattr(style, 'prompt', ''), "negative": getattr(style, 'negative_prompt', ''), "extra": getattr(style, 'extra', ''), + "wildcards": getattr(style, 'wildcards', ''), "local_preview": f"{fn}.{shared.opts.samples_format}", "onclick": '"' + html.escape(f"""return selectStyle({json.dumps(name)})""") + '"', "mtime": getattr(style, 'mtime', 0),