From 23593d5442b36695c00b673e9ea544c7648eb6f0 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 28 Sep 2023 13:32:56 -0400 Subject: [PATCH] en styles phase two --- CHANGELOG.md | 20 ++- .../Lora/ui_extra_networks_lora.py | 1 + extensions-builtin/sd-extension-system-info | 2 +- html/locale_en.json | 11 +- javascript/extraNetworks.js | 35 ++++-- javascript/style.css | 5 +- modules/images.py | 8 +- modules/shared.py | 16 ++- modules/styles.py | 2 + modules/ui_extra_networks.py | 118 +++++++++++++----- modules/ui_extra_networks_checkpoints.py | 1 + modules/ui_extra_networks_hypernets.py | 1 + modules/ui_extra_networks_styles.py | 52 ++++++++ .../ui_extra_networks_textual_inversion.py | 1 + modules/ui_models.py | 21 +++- modules/ui_symbols.py | 1 + 16 files changed, 227 insertions(+), 68 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cc4f754d..31d7f9063 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,11 +14,17 @@ Upgrades are still possible and supported, but above is recommended for best exp any ui state including state of open/closed menus can be saved as default! see *System -> User interface -> Set menu states* - **Extra networks**: + - you can scan [civitai](https://civitai.com/) + for missing metadata and previews directly from extra networks - new details interface to view and save data about extra networks main ui now has a single button on each en to trigger details view - details view includes model/lora metadata parser! - - details view includes civitai model metadata! - - styles can be edited in details view + - details view includes civitai model metadata! + - **Styles** + - save/apply icons moved to extra networks + - can be edited in details view + - support for single or multiple styles per json + - support for embedded previews - faster search, ability to show/hide/sort networks - refactored subfolder handling *note*: this will trigger model hash recaclulation on first model use @@ -44,6 +50,9 @@ Upgrades are still possible and supported, but above is recommended for best exp - better **TI embeddings** support for SD and SDXL faster loading, wider compatibility and support for embeddings with multiple vectors information about used embedding is now also added to image metadata + thanks @AI-Casanova + - better **Lora** handling + thanks @AI-Casanova - **Upscalers**: - fix long outstanding memory leak in legacy code, amazing this went undetected for so long - more high quality upscalers available by default @@ -53,7 +62,8 @@ Upgrades are still possible and supported, but above is recommended for best exp Note: Recommended usage for *SD Upscale* is by using second pass instead of upscaler as it allows for tuning of prompt, seed, sampler settings which are used to guide upscaler - upscalers are available in **xyz grid** - - simplified *settings->postprocessing->upscalers* + - simplified *settings->postprocessing->upscalers* + e.g. all upsamplers share same settings for tiling - allow upscale-only as part of **txt2img** and **img2img** workflows simply set *denoising strength* to 0 so hires does not get triggered - unified init/download/execute/progress code @@ -62,6 +72,7 @@ Upgrades are still possible and supported, but above is recommended for best exp - moved ui options to submenu - default list for new installs is now all samplers, list can be modified in settings - simplified samplers configuration in settings + plus added few new ones like sigma min/max which can highly impact sampler behavior - note that list of samplers is now *different* since keeping a flat-list of all possible combinations results in 50+ samplers which is not practical items such as algorithm (e.g. karras) is actually a sampler option, not a sampler itself @@ -82,6 +93,9 @@ Upgrades are still possible and supported, but above is recommended for best exp - updated installer to support detect `ROCm` *5.4/5.5/5.6/5.7* - **CUDA**: - testing moved to `torch` *2.2.0-dev/cu121* + - **GC**: + - custom garbage collect threshold to reduce vram memory usage, thanks @Disty0 + see *settings -> compute -> gc* - **Startup** - All main CLI parameters can now be set as environment variable as well for example `--data-dir ` can be specified as `SD_DATADIR=` before starting SD.Next diff --git a/extensions-builtin/Lora/ui_extra_networks_lora.py b/extensions-builtin/Lora/ui_extra_networks_lora.py index e416f959d..7a429832e 100644 --- a/extensions-builtin/Lora/ui_extra_networks_lora.py +++ b/extensions-builtin/Lora/ui_extra_networks_lora.py @@ -24,6 +24,7 @@ class ExtraNetworksPageLora(ui_extra_networks.ExtraNetworksPage): tags[' '.join(words[1:])] = words[0] name = os.path.splitext(os.path.relpath(l.filename, shared.cmd_opts.lora_dir))[0] yield { + "type": 'Lora', "name": name, "filename": l.filename, "hash": l.shorthash, diff --git a/extensions-builtin/sd-extension-system-info b/extensions-builtin/sd-extension-system-info index b73ffdcc8..75b6c5a6c 160000 --- a/extensions-builtin/sd-extension-system-info +++ b/extensions-builtin/sd-extension-system-info @@ -1 +1 @@ -Subproject commit b73ffdcc8d6a9622d3d8074862b109b20726ac73 +Subproject commit 75b6c5a6cdb2bd44e03e3bf30b65461c600e0d67 diff --git a/html/locale_en.json b/html/locale_en.json index 1e8efe127..4e013b0e8 100644 --- a/html/locale_en.json +++ b/html/locale_en.json @@ -1,23 +1,24 @@ {"icons": [ - {"id":"","label":"📘","localized":"","hint":"Read generation parameters from prompt or last generation if prompt is empty into user interface"}, + {"id":"","label":"📘","localized":"","hint":"Read parameters from last generated image"}, {"id":"","label":"🚮","localized":"","hint":"Clear prompt"}, {"id":"","label":"🌐","localized":"","hint":"Show/hide extra networks"}, {"id":"","label":"⏩","localized":"","hint":"Apply selected styles to current prompt"}, - {"id":"","label":"⬇️","localized":"","hint":"Save current prompt as style template"}, + {"id":"","label":"⬇️","localized":"","hint":"Save parameters from last generated image as style template"}, {"id":"","label":"🔄","localized":"","hint":"Refresh"}, {"id":"","label":"🛗","localized":"","hint":"Close"}, {"id":"","label":"⏫","localized":"","hint":"Fill"}, {"id":"","label":"🎲️","localized":"","hint":"Use random seed"}, {"id":"","label":"♻️","localized":"","hint":"Reuse previous seed"}, {"id":"","label":"⇅","localized":"","hint":"Swap values"}, - {"id":"","label":"⇦","localized":"","hint":"Read generation parameters from prompt or last generation if prompt is empty into user interface"}, + {"id":"","label":"⇦","localized":"","hint":"Read parameters from last generated image"}, {"id":"","label":"⊗","localized":"","hint":"Clear prompt"}, {"id":"","label":"🗁","localized":"","hint":"Show/hide extra networks"}, {"id":"","label":"⇰","localized":"","hint":"Apply selected styles to current prompt"}, - {"id":"","label":"⇩","localized":"","hint":"Save current prompt as style template"}, + {"id":"","label":"⇩","localized":"","hint":"Save parameters from last generated image as style template"}, {"id":"","label":"⟲","localized":"","hint":"Refresh"}, - {"id":"","label":"🗙","localized":"","hint":"Close"}, + {"id":"","label":"✕","localized":"","hint":"Close"}, {"id":"","label":"⊜","localized":"","hint":"Fill"}, + {"id":"","label":"🕸️","localized":"","hint":"Scan CivitAI for missing metadata and previews"}, {"id":"","label":"📐","localized":"","hint":"Measure"}, {"id":"","label":"🔍","localized":"","hint":"Search"} ], diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index fc4749c80..ff9c023a8 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -184,26 +184,35 @@ function applyStyles(styles) { function setupExtraNetworksForTab(tabname) { gradioApp().querySelector(`#${tabname}_extra_tabs`).classList.add('extra-networks'); - const tabs = gradioApp().querySelector(`#${tabname}_extra_tabs > div`); - const search = gradioApp().querySelector(`#${tabname}_extra_search textarea`); - const refresh = gradioApp().getElementById(`${tabname}_extra_refresh`); - const description = gradioApp().getElementById(`${tabname}_description`); - const close = gradioApp().getElementById(`${tabname}_extra_close`); const en = gradioApp().getElementById(`${tabname}_extra_networks`); - search.classList.add('search'); - description.classList.add('description'); - tabs.appendChild(refresh); - tabs.appendChild(close); + const tabs = gradioApp().querySelector(`#${tabname}_extra_tabs > div`); + const txtSearch = gradioApp().querySelector(`#${tabname}_extra_search textarea`); + const txtDescription = gradioApp().getElementById(`${tabname}_description`); + const btnRefresh = gradioApp().getElementById(`${tabname}_extra_refresh`); + const btnScan = gradioApp().getElementById(`${tabname}_extra_scan`); + const btnSave = gradioApp().getElementById(`${tabname}_extra_save`); + const btnApply = gradioApp().getElementById(`${tabname}_extra_apply`); + const btnClose = gradioApp().getElementById(`${tabname}_extra_close`); + txtSearch.classList.add('search'); + txtDescription.classList.add('description'); + const buttons = document.createElement('span'); + buttons.classList.add('buttons'); + buttons.appendChild(btnRefresh); + buttons.appendChild(btnScan); + buttons.appendChild(btnSave); + buttons.appendChild(btnApply); + buttons.appendChild(btnClose); + tabs.appendChild(buttons); const div = document.createElement('div'); div.classList.add('second-line'); tabs.appendChild(div); - div.appendChild(search); - div.appendChild(description); + div.appendChild(txtSearch); + div.appendChild(txtDescription); let searchTimer = null; - search.addEventListener('input', (evt) => { + txtSearch.addEventListener('input', (evt) => { if (searchTimer) clearTimeout(searchTimer); searchTimer = setTimeout(() => { - filterExtraNetworksForTab(tabname, search.value.toLowerCase()); + filterExtraNetworksForTab(tabname, txtSearch.value.toLowerCase()); searchTimer = null; }, 150); }); diff --git a/javascript/style.css b/javascript/style.css index c29814f20..30fd3fb9c 100644 --- a/javascript/style.css +++ b/javascript/style.css @@ -239,11 +239,12 @@ table.settings-value-table td { padding: 0.4em; border: 1px solid #ccc; max-widt .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; } +.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; } .extra-networks-page { display: flex } -.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-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; } diff --git a/modules/images.py b/modules/images.py index 98b44c4d0..517fde3a3 100644 --- a/modules/images.py +++ b/modules/images.py @@ -303,10 +303,10 @@ class FilenameGenerator: 'height': lambda self: self.image.height, 'image_hash': lambda self: self.image_hash(), 'job_timestamp': lambda self: getattr(self.p, "job_timestamp", shared.state.job_timestamp), - 'model': lambda self: sanitize_filename_part(shared.sd_model.sd_checkpoint_info.name_for_extra, replace_spaces=False), - 'model_shortname': lambda self: sanitize_filename_part(shared.sd_model.sd_checkpoint_info.name_for_extra, replace_spaces=False), - 'model_hash': lambda self: getattr(self.p, "sd_model_hash", shared.sd_model.sd_model_hash), - 'model_name': lambda self: sanitize_filename_part(shared.sd_model.sd_checkpoint_info.model_name, replace_spaces=False), + 'model': lambda self: sanitize_filename_part(shared.sd_model.sd_checkpoint_info.title, replace_spaces=False), + 'model_shortname': lambda self: sanitize_filename_part(shared.sd_model.sd_checkpoint_info.name, replace_spaces=False), + 'model_hash': lambda self: shared.sd_model.sd_checkpoint_info.shorthash, + 'model_name': lambda self: sanitize_filename_part(shared.sd_model.sd_checkpoint_info.name, replace_spaces=False), 'prompt_hash': lambda self: hashlib.sha256(self.prompt.encode()).hexdigest()[0:8], 'prompt_no_styles': lambda self: self.prompt_no_style(), 'prompt_spaces': lambda self: sanitize_filename_part(self.prompt, replace_spaces=False), diff --git a/modules/shared.py b/modules/shared.py index 0b0954a1f..4ef2e4733 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -332,12 +332,13 @@ def readfile(filename, silent=False): if not silent: log.debug(f'Reading: {filename} len={len(data)}') except Exception as e: - log.error(f'Reading failed: {filename} {e}') + if not silent: + log.error(f'Reading failed: {filename} {e}') + return {} return data def writefile(data, filename, mode='w', silent=False): - def default(obj): log.error(f"Saving: {filename} not a valid object: {obj}") return str(obj) @@ -345,7 +346,16 @@ def writefile(data, filename, mode='w', silent=False): try: with fasteners.InterProcessLock(f"{filename}.lock"): # skipkeys=True, ensure_ascii=True, check_circular=True, allow_nan=True - output = json.dumps(data, indent=2, default=default) + if type(data) == dict: + output = json.dumps(data, indent=2, default=default) + elif isinstance(data, object): + simple = {} + for k in data.__dict__: + if data.__dict__[k] is not None: + simple[k] = data.__dict__[k] + output = json.dumps(simple, indent=2, default=default) + else: + raise ValueError('not a valid object') if not silent: log.debug(f'Saving: {filename} len={len(output)}') with open(filename, mode, encoding="utf8") as file: diff --git a/modules/styles.py b/modules/styles.py index d8a3315fd..83a31b85f 100644 --- a/modules/styles.py +++ b/modules/styles.py @@ -69,6 +69,8 @@ class StyleDatabase: if type(all_styles) is dict: all_styles = [all_styles] for style in all_styles: + if type(style) is not dict or "name" not in style: + raise ValueError('cannot parse style') basename = os.path.splitext(os.path.basename(fn))[0] name = re.sub(r'[\t\r\n]', '', style.get("name", basename)).strip() name = os.path.join(os.path.dirname(os.path.relpath(fn, self.path)), name) diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 1f386d1fc..7aece8562 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -17,7 +17,6 @@ from modules import shared, scripts, modelloader from modules.ui_components import ToolButton import modules.ui_symbols as symbols - allowed_dirs = [] dir_cache = {} # key=path, value=(mtime, listdir(path)) refresh_time = None @@ -268,6 +267,8 @@ class ExtraNetworksPage: return 'html/card-no-preview.png' def find_preview(self, path): + if path is None: + return self.link_preview('html/card-no-preview.png') fn = os.path.splitext(path)[0] preview_extensions = ["jpg", "jpeg", "png", "webp", "tiff", "jp2"] for file in [f'{fn}{mid}{ext}' for ext in preview_extensions for mid in ['.thumb.', '.', '.preview.']]: @@ -353,8 +354,14 @@ class ExtraNetworksUi: self.description: gr.Textbox = None self.search: gr.Textbox = None self.button_details: gr.Button = None + self.button_refresh: gr.Button = None + self.button_scan: gr.Button = None + self.button_save: gr.Button = None + self.button_apply: gr.Button = None + self.button_close: gr.Button = None self.details_components: list = [] self.last_item: dict = None + self.last_page: ExtraNetworksPage = None def create_ui(container, button_parent, tabname, skip_indexing = False): @@ -368,16 +375,22 @@ def create_ui(container, button_parent, tabname, skip_indexing = False): ui.button_details = gr.Button('Details', elem_id=tabname+"_extra_details_btn", visible=False) state = {} - def get_item(state): - if state is None or not hasattr(state, 'page') or not hasattr(state, 'item'): - return None, None - page = next(iter([x for x in get_pages() if x.title == state.page]), None) - if page is None: - return None, None - item = next(iter([x for x in page.items if x["name"] == state.item]), None) - if item is None: - return page, None + def get_item(state, params = None): + if params is not None and type(params) == dict: + page = next(iter([x for x in get_pages() if x.title == 'Style']), None) + item = page.create_style(params) + else: + if state is None or not hasattr(state, 'page') or not hasattr(state, 'item'): + return None, None + page = next(iter([x for x in get_pages() if x.title == state.page]), None) + if page is None: + return None, None + item = next(iter([x for x in page.items if x["name"] == state.item]), None) + if item is None: + return page, None item = SimpleNamespace(**item) + ui.last_item = item + ui.last_page = page return page, item # main event that is triggered when js updates state text field with json values, used to communicate js -> python @@ -388,9 +401,8 @@ def create_ui(container, button_parent, tabname, skip_indexing = False): except Exception as e: shared.log.error(f'Extra networks state error: {e}') return - _page, item = get_item(state) + _page, _item = get_item(state) # shared.log.debug(f'Extra network: op={state.op} page={page.title if page is not None else None} item={item.filename if item is not None else None}') - ui.last_item = item def toggle_visibility(is_visible): is_visible = not is_visible @@ -427,8 +439,17 @@ def create_ui(container, button_parent, tabname, skip_indexing = False): ui.details_components.append(meta) with ui.tabs: - button_refresh = ToolButton(symbols.refresh, elem_id=tabname+"_extra_refresh") - button_close = ToolButton(symbols.close, elem_id=tabname+"_extra_close") + def ui_tab_change(page): + scan_visible = page in ['Model', 'Lora', 'Hypernetwork', 'Embedding'] + save_visible = page in ['Style'] + apply_visible = page in ['Style'] + return [gr.update(visible=scan_visible), gr.update(visible=save_visible), gr.update(visible=apply_visible)] + + ui.button_refresh = ToolButton(symbols.refresh, elem_id=tabname+"_extra_refresh") + ui.button_scan = ToolButton(symbols.scan, elem_id=tabname+"_extra_scan", visible=True) + ui.button_save = ToolButton(symbols.save, elem_id=tabname+"_extra_save", visible=False) + ui.button_apply = ToolButton(symbols.apply, elem_id=tabname+"_extra_apply", visible=False) + ui.button_close = ToolButton(symbols.close, elem_id=tabname+"_extra_close") ui.search = gr.Textbox('', show_label=False, elem_id=tabname+"_extra_search", placeholder="Search...", elem_classes="textbox", lines=2) ui.description = gr.Textbox('', show_label=False, elem_id=tabname+"_description", elem_classes="textbox", lines=2, interactive=False) @@ -437,9 +458,12 @@ def create_ui(container, button_parent, tabname, skip_indexing = False): refresh_time = time.time() for page in get_pages(): page.create_page(ui.tabname, skip_indexing) - with gr.Tab(page.title, id=page.title.lower().replace(" ", "_"), elem_classes="extra-networks-tab"): + with gr.Tab(page.title, id=page.title.lower().replace(" ", "_"), elem_classes="extra-networks-tab") as tab: hmtl = gr.HTML(page.html, elem_id=f'{tabname}{page.name}_extra_page', elem_classes="extra-networks-page") ui.pages.append(hmtl) + tab.select(ui_tab_change, _js="getENActivePage", inputs=[ui.button_details], outputs=[ui.button_scan, ui.button_save, ui.button_apply]) + + # ui.tabs.change(fn=ui_tab_change, inputs=[], outputs=[ui.button_scan, ui.button_save]) def fn_save_img(): if ui.last_item is None or ui.last_item.local_preview is None: @@ -472,16 +496,24 @@ def create_ui(container, button_parent, tabname, skip_indexing = False): return 'html/card-no-preview.png' def fn_save_desc(desc): - fn = os.path.splitext(ui.last_item.filename)[0] + '.txt' - with open(fn, 'w', encoding='utf-8') as f: - f.write(desc) - shared.log.debug(f'Extra network save desc: item={ui.last_item.name} filename={fn}') + if hasattr(ui.last_item, 'type') and ui.last_item.type == 'Style': + params = ui.last_page.parse_desc(desc) + if params is not None: + fn_save_info(params) + else: + fn = os.path.splitext(ui.last_item.filename)[0] + '.txt' + with open(fn, 'w', encoding='utf-8') as f: + f.write(desc) + shared.log.debug(f'Extra network save desc: item={ui.last_item.name} filename={fn}') return desc def fn_delete_desc(desc): if ui.last_item is None: return desc - fn = os.path.splitext(ui.last_item.filename)[0] + '.txt' + 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' if os.path.exists(fn): shared.log.debug(f'Extra network delete desc: item={ui.last_item.name} filename={fn}') os.remove(fn) @@ -491,7 +523,7 @@ def create_ui(container, button_parent, tabname, skip_indexing = False): def fn_save_info(info): fn = os.path.splitext(ui.last_item.filename)[0] + '.json' shared.writefile(info, fn, silent=True) - shared.log.debug(f'Extra network save desc: item={ui.last_item.name} filename={fn}') + shared.log.debug(f'Extra network save info: item={ui.last_item.name} filename={fn}') return info def fn_delete_info(info): @@ -511,10 +543,10 @@ def create_ui(container, button_parent, tabname, skip_indexing = False): btn_save_info.click(fn=fn_save_info, inputs=[info], outputs=[info]) btn_delete_info.click(fn=fn_delete_info, inputs=[info], outputs=[info]) - def show_details(text, img, desc, info, meta): - page, item = get_item(state) - if item is not None and os.path.exists(item.filename): - stat = os.stat(item.filename) + def show_details(text, img, desc, info, meta, params): + 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 desc = item.description fullinfo = shared.readfile(os.path.splitext(item.filename)[0] + '.json', silent=True) if 'modelVersions' in fullinfo: # sanitize massive objects @@ -530,6 +562,9 @@ def create_ui(container, button_parent, tabname, skip_indexing = False): lora = '' model = '' style = '' + note = '' + if not os.path.exists(item.filename): + note = f'

File is not yet saved

Target filename: {item.filename}' if page.title == 'Model': merge = len(list(meta.get('sd_merge_models', {}))) if merge > 0: @@ -565,17 +600,18 @@ def create_ui(container, button_parent, tabname, skip_indexing = False): Alias{getattr(item, 'alias', 'N/A')} Filename{item.filename} Hash{getattr(item, 'hash', 'N/A')} - Size{round(stat.st_size/1024/1024, 2)} MB - Last modified{datetime.fromtimestamp(stat.st_mtime)} + Size{round(stat.st_size/1024/1024, 2) if stat is not None else 'N/A'} MB + Last modified{datetime.fromtimestamp(stat.st_mtime) if stat is not None else 'N/A'} {lora} {model} {style} + {note} ''' return [text, img, desc, info, meta, gr.update(visible=item is not None)] - def en_refresh(title): + def ui_refresh_click(title): pages = [] for page in get_pages(): if title is None or title == '' or title == page.title or len(page.html) == 0: @@ -587,11 +623,31 @@ def create_ui(container, button_parent, tabname, skip_indexing = False): ui.search.update(value = ui.search.value) return pages + def ui_scan_click(title): + from modules import ui_models + ui_models.search_metadata_civit(True, title) + return ui_refresh_click(title) + + def ui_save_click(): + from modules import paths, generation_parameters_copypaste + filename = os.path.join(paths.data_path, "params.txt") + if os.path.exists(filename): + with open(filename, "r", encoding="utf8") as file: + prompt = file.read() + 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) + return res + + dummy_state = gr.State(value=False) # pylint: disable=abstract-class-instantiated button_parent.click(fn=toggle_visibility, inputs=[ui.visible], outputs=[ui.visible, container, button_parent]) - button_close.click(fn=toggle_visibility, inputs=[ui.visible], outputs=[ui.visible, container]) - button_refresh.click(_js='getENActivePage', fn=en_refresh, inputs=[ui.search], outputs=ui.pages) + ui.button_close.click(fn=toggle_visibility, inputs=[ui.visible], outputs=[ui.visible, container]) + ui.button_refresh.click(fn=ui_refresh_click, _js='getENActivePage', inputs=[ui.search], outputs=ui.pages) + 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_details.click(show_details, _js="getCardDetails", inputs=ui.details_components + [dummy_state], outputs=ui.details_components + [ui.details]) ui.state.change(state_change, inputs=[ui.state], outputs=[]) - ui.button_details.click(show_details, _js="getCardDetails", inputs=ui.details_components, outputs=ui.details_components + [ui.details]) return ui diff --git a/modules/ui_extra_networks_checkpoints.py b/modules/ui_extra_networks_checkpoints.py index 4a2eb33e0..d3a157cae 100644 --- a/modules/ui_extra_networks_checkpoints.py +++ b/modules/ui_extra_networks_checkpoints.py @@ -17,6 +17,7 @@ class ExtraNetworksPageCheckpoints(ui_extra_networks.ExtraNetworksPage): for name, checkpoint in sd_models.checkpoints_list.items(): fn = os.path.splitext(checkpoint.filename)[0] record = { + "type": 'Model', "name": checkpoint.name, "title": checkpoint.title, "filename": checkpoint.filename, diff --git a/modules/ui_extra_networks_hypernets.py b/modules/ui_extra_networks_hypernets.py index 51fafb56d..e8f5dd675 100644 --- a/modules/ui_extra_networks_hypernets.py +++ b/modules/ui_extra_networks_hypernets.py @@ -15,6 +15,7 @@ class ExtraNetworksPageHypernetworks(ui_extra_networks.ExtraNetworksPage): fn = os.path.splitext(path)[0] name = os.path.relpath(fn, shared.opts.hypernetwork_dir) yield { + "type": 'Hypernetwork', "name": os.path.relpath(fn, shared.opts.hypernetwork_dir), "filename": path, "preview": self.find_preview(fn), diff --git a/modules/ui_extra_networks_styles.py b/modules/ui_extra_networks_styles.py index 7980b1e8c..5b36d6740 100644 --- a/modules/ui_extra_networks_styles.py +++ b/modules/ui_extra_networks_styles.py @@ -44,6 +44,57 @@ class ExtraNetworksPageStyles(ui_extra_networks.ExtraNetworksPage): pass """ + def parse_desc(self, desc): + lines = desc.strip().split("\n") + params = { 'name': '', 'description': '', 'prompt': '', 'negative': '', 'extra': ''} + found = '' + for line in lines: + line = line.strip() + if line.lower().startswith('name:'): + found = 'name' + params['name'] = line[5:].strip() + elif line.lower().startswith('description:'): + found = 'description' + params['description'] = line[12:].strip() + elif line.lower().startswith('prompt:'): + found = 'prompt' + params['prompt'] = line[7:].strip() + elif line.lower().startswith('negative:'): + found = 'negative' + params['negative'] = line[9:].strip() + elif line.lower().startswith('extra:'): + found = 'extra' + params['extra'] = line[6:].strip() + elif found != '': + params[found] += '\n' + line + if params['name'] == '': + return None + if params['description'] == '': + params['description'] = params['name'] + return params + + def create_style(self, params): + from modules.images import FilenameGenerator + from hashlib import sha256 + namegen = FilenameGenerator(p=None, seed=None, prompt=params.get('Prompt', ''), image=None, grid=False) + name = namegen.prompt_words() + sha = sha256(json.dumps(name).encode()).hexdigest()[0:8] + fn = os.path.join(shared.opts.styles_dir, sha + '.json') + item = { + "type": 'Style', + "name": name, + "title": name, + "filename": fn, + "search_term": f'{self.search_terms_from_path(name)}', + "preview": self.find_preview(name), + "description": '', + "prompt": params.get('Prompt', ''), + "negative": params.get('Negative prompt', ''), + "extra": '', # TODO add extras to styles + "local_preview": f"{name}.{shared.opts.samples_format}", + } + return item + def list_items(self): for k, style in shared.prompt_styles.styles.items(): fn = os.path.splitext(style.filename)[0] @@ -51,6 +102,7 @@ class ExtraNetworksPageStyles(ui_extra_networks.ExtraNetworksPage): if len(style.negative_prompt) > 0: txt += f'\nNegative: {style.negative_prompt}' yield { + "type": 'Style', "name": style.name, "title": k, "filename": style.filename, diff --git a/modules/ui_extra_networks_textual_inversion.py b/modules/ui_extra_networks_textual_inversion.py index dc892e572..578b6fcf0 100644 --- a/modules/ui_extra_networks_textual_inversion.py +++ b/modules/ui_extra_networks_textual_inversion.py @@ -46,6 +46,7 @@ class ExtraNetworksPageTextualInversion(ui_extra_networks.ExtraNetworksPage): tags[embedding.tag]=1 name = os.path.splitext(embedding.basename)[0] yield { + "type": 'Embedding', "name": name, "filename": embedding.filename, "preview": self.find_preview(path), diff --git a/modules/ui_models.py b/modules/ui_models.py index 45292e681..fd02fda17 100644 --- a/modules/ui_models.py +++ b/modules/ui_models.py @@ -11,6 +11,9 @@ import modules.errors import modules.hashes +search_metadata_civit = None + + def create_ui(): dummy_component = gr.Label(visible=False) @@ -241,7 +244,7 @@ def create_ui(): with gr.Tab(label="CivitAI"): data = [] - def civit_search(name, tag, model_type): + def civit_search_model(name, tag, model_type): types = 'LORA' if model_type == 'LoRA' else 'Checkpoint' url = f'https://civitai.com/api/v1/models?limit=25&types={types}&Sort=Newest' if name is not None and len(name) > 0: @@ -335,12 +338,15 @@ def create_ui(): list_models() return res - def civit_download_previews(civit_previews_rehash): + def civit_search_metadata(civit_previews_rehash, title): log.debug('CivitAI download previews') from modules.ui_extra_networks import get_pages from modules.modelloader import download_civit_preview, download_civit_meta res = [] for page in get_pages(): + if type(title) == str: + if page.title != title: + continue if page.name == 'style': continue for item in page.list_items(): @@ -371,6 +377,9 @@ def create_ui(): txt = '
'.join([r for r in res if len(r) > 0]) return txt + global search_metadata_civit # pylint: disable=global-statement + search_metadata_civit = civit_search_metadata + with gr.Row(style={'margin-top': '1em'}): gr.HTML('

Fetch information

Fetches preview and metadata information for all models with missing information
Models with existing previews and information are not updated
') with gr.Row(): @@ -408,11 +417,11 @@ def create_ui(): civit_types1 = ['number', 'str', 'str', 'number', 'number'] civit_results1 = gr.DataFrame(value = None, label = 'Search results', show_label = True, interactive = False, wrap = True, overflow_row_behaviour = 'paginate', max_rows = 10, headers = civit_headers1, datatype = civit_types1, type='array') - civit_search_text.submit(fn=civit_search, inputs=[civit_search_text, civit_search_tag, civit_model_type], outputs=[civit_results1, civit_results2, civit_results3]) - civit_search_tag.submit(fn=civit_search, inputs=[civit_search_text, civit_search_tag, civit_model_type], outputs=[civit_results1, civit_results2, civit_results3]) - civit_search_btn.click(fn=civit_search, inputs=[civit_search_text, civit_search_tag, civit_model_type], outputs=[civit_results1, civit_results2, civit_results3]) + civit_search_text.submit(fn=civit_search_model, inputs=[civit_search_text, civit_search_tag, civit_model_type], outputs=[civit_results1, civit_results2, civit_results3]) + civit_search_tag.submit(fn=civit_search_model, inputs=[civit_search_text, civit_search_tag, civit_model_type], outputs=[civit_results1, civit_results2, civit_results3]) + civit_search_btn.click(fn=civit_search_model, inputs=[civit_search_text, civit_search_tag, civit_model_type], outputs=[civit_results1, civit_results2, civit_results3]) civit_results1.select(fn=civit_select1, inputs=[civit_results1], outputs=[civit_results2, models_image]) civit_results2.select(fn=civit_select2, inputs=[civit_results2], outputs=[civit_results3]) civit_results3.select(fn=civit_select3, inputs=[civit_results3], outputs=[civit_selected, civit_name, civit_search_btn]) civit_download_model_btn.click(fn=civit_download_model, inputs=[civit_selected, civit_name, civit_path, civit_model_type, models_image], outputs=[models_outcome]) - civit_previews_btn.click(fn=civit_download_previews, inputs=[civit_previews_rehash], outputs=[models_outcome]) + civit_previews_btn.click(fn=civit_search_metadata, inputs=[civit_previews_rehash, civit_previews_rehash], outputs=[models_outcome]) diff --git a/modules/ui_symbols.py b/modules/ui_symbols.py index c58aa5211..f5a51215e 100644 --- a/modules/ui_symbols.py +++ b/modules/ui_symbols.py @@ -5,6 +5,7 @@ save = '⇩' apply = '⇰' clear = '⊗' fill = '⊜' +scan = '🕸️' networks = '🌐' paste = '⇦'