diff --git a/extensions-builtin/Lora/lora.py b/extensions-builtin/Lora/lora.py index 40863787b..32f55eafe 100644 --- a/extensions-builtin/Lora/lora.py +++ b/extensions-builtin/Lora/lora.py @@ -211,7 +211,11 @@ def load_loras(names, multipliers=None): lora_on_disk = loras_on_disk[i] if lora_on_disk is not None: if lora is None or os.path.getmtime(lora_on_disk.filename) > lora.mtime: - lora = load_lora(name, lora_on_disk.filename) + try: + lora = load_lora(name, lora_on_disk.filename) + except Exception as e: + errors.display(e, f"loading Lora {lora_on_disk.filename}") + continue if lora is None: print(f"Couldn't find Lora with name {name}") diff --git a/javascript/imageviewer.js b/javascript/imageviewer.js index 149177430..96714b573 100644 --- a/javascript/imageviewer.js +++ b/javascript/imageviewer.js @@ -106,7 +106,8 @@ function setupImageForLightbox(e) { var event = isFirefox ? 'mousedown' : 'click' e.addEventListener(event, function (evt) { if (evt.button != 0) return; - modalZoomSet(gradioApp().getElementById('modalImage'), true) + initialZoom = (localStorage.getItem('modalZoom') || true) == 'yes' + modalZoomSet(gradioApp().getElementById('modalImage'), initialZoom) evt.preventDefault() showModal(evt) }, true); @@ -115,6 +116,7 @@ function setupImageForLightbox(e) { function modalZoomSet(modalImage, enable) { if (enable) modalImage.classList.add('modalImageFullscreen'); else modalImage.classList.remove('modalImageFullscreen'); + localStorage.setItem('modalZoom', enable ? 'yes' : 'no') } function modalZoomToggle(event) { diff --git a/modules/call_queue.py b/modules/call_queue.py index 43aaf9fc0..479f6bffc 100644 --- a/modules/call_queue.py +++ b/modules/call_queue.py @@ -36,6 +36,7 @@ def wrap_gradio_gpu_call(func, extra_outputs=None): try: res = func(*args, **kwargs) + progress.record_results(id_task, res) finally: progress.finish_task(id_task) diff --git a/modules/interrogate.py b/modules/interrogate.py index d355d70eb..9892d326f 100644 --- a/modules/interrogate.py +++ b/modules/interrogate.py @@ -34,7 +34,7 @@ def download_default_clip_interrogate_categories(content_dir): cat_types = ["artists", "flavors", "mediums", "movements"] try: - os.makedirs(tmpdir) + os.makedirs(tmpdir, exist_ok=True) for category_type in cat_types: torch.hub.download_url_to_file(f"https://raw.githubusercontent.com/pharmapsychotic/clip-interrogator/main/clip_interrogator/data/{category_type}.txt", os.path.join(tmpdir, f"{category_type}.txt")) os.rename(tmpdir, content_dir) @@ -43,7 +43,7 @@ def download_default_clip_interrogate_categories(content_dir): errors.display(e, "downloading default CLIP interrogate categories") finally: if os.path.exists(tmpdir): - os.remove(tmpdir) + os.removedirs(tmpdir) class InterrogateModels: diff --git a/modules/middleware.py b/modules/middleware.py index 74c12303c..7e29a2c91 100644 --- a/modules/middleware.py +++ b/modules/middleware.py @@ -39,7 +39,7 @@ def setup_middleware(app: FastAPI, cmd_opts): res.headers["X-Process-Time"] = duration endpoint = req.scope.get('path', 'err') if cmd_opts.api_log and endpoint.startswith('/sdapi'): - log.info('API {t} {code} {prot}/{ver} {method} {endpoint} {cli} {duration}'.format( # pylint: disable=consider-using-f-string + log.info('API {t} {code} {prot}/{ver} {method} {endpoint} {cli} {duration}'.format( # pylint: disable=consider-using-f-string, logging-format-interpolation t = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f"), code = res.status_code, ver = req.scope.get('http_version', '0.0'), @@ -58,8 +58,8 @@ def setup_middleware(app: FastAPI, cmd_opts): "body": vars(e).get('body', ''), "errors": str(e), } - log.error(f"API error: {req.method}: {req.url} {err}") if not isinstance(e, HTTPException) and err['error'] != 'TypeError': # do not print backtrace on known httpexceptions + log.error(f"API error: {req.method}: {req.url} {err}") errors.display(e, 'HTTP API', [anyio, fastapi, uvicorn, starlette]) return JSONResponse(status_code=vars(e).get('status_code', 500), content=jsonable_encoder(err)) diff --git a/modules/postprocessing.py b/modules/postprocessing.py index cceb3b463..6b6fe3b07 100644 --- a/modules/postprocessing.py +++ b/modules/postprocessing.py @@ -20,9 +20,14 @@ def run_postprocessing(extras_mode, image, image_folder: List[tempfile.NamedTemp if extras_mode == 1: for img in image_folder: - image = Image.open(os.path.abspath(img.name)) + if isinstance(img, Image.Image): + image = img + fn = '' + else: + image = Image.open(os.path.abspath(img.name)) + fn = os.path.splitext(img.orig_name)[0] image_data.append(image) - image_names.append(os.path.splitext(img.orig_name)[0]) + image_names.append(fn) elif extras_mode == 2: assert not shared.cmd_opts.hide_ui_dir_config, '--hide-ui-dir-config option must be disabled' assert input_dir, 'input directory not selected' diff --git a/modules/progress.py b/modules/progress.py index 93246eb08..42ed01052 100644 --- a/modules/progress.py +++ b/modules/progress.py @@ -8,6 +8,8 @@ import modules.shared as shared current_task = None pending_tasks = {} finished_tasks = [] +recorded_results = [] +recorded_results_limit = 2 def start_task(id_task): @@ -16,6 +18,12 @@ def start_task(id_task): pending_tasks.pop(id_task, None) +def record_results(id_task, res): + recorded_results.append((id_task, res)) + if len(recorded_results) > recorded_results_limit: + recorded_results.pop(0) + + def finish_task(id_task): global current_task # pylint: disable=global-statement if current_task == id_task: diff --git a/modules/sd_models.py b/modules/sd_models.py index ed570c909..f9094f6e0 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -50,7 +50,14 @@ class CheckpointInfo: self.shorthash = self.sha256[0:10] if self.sha256 else None self.title = name if self.shorthash is None else f'{name} [{self.shorthash}]' self.ids = [self.hash, self.model_name, self.title, name, f'{name} [{self.hash}]'] + ([self.shorthash, self.sha256, f'{self.name} [{self.shorthash}]'] if self.shorthash else []) - + self.metadata = {} + _, ext = os.path.splitext(self.filename) + if ext.lower() == ".safetensors": + try: + self.metadata = read_metadata_from_safetensors(filename) + except Exception as e: + errors.display(e, f"reading checkpoint metadata: {filename}") + def register(self): checkpoints_list[self.title] = self for i in self.ids: diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index d4e210e62..95ebfb4f0 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -230,6 +230,12 @@ class EmbeddingDatabase: self.load_from_dir(embdir) embdir.update() + # re-sort word_embeddings because load_from_dir may not load in alphabetic order. + # using a temporary copy so we don't reinitialize self.word_embeddings in case other objects have a reference to it. + sorted_word_embeddings = {e.name: e for e in sorted(self.word_embeddings.values(), key=lambda e: e.name.lower())} + self.word_embeddings.clear() + self.word_embeddings.update(sorted_word_embeddings) + displayed_embeddings = (tuple(self.word_embeddings.keys()), tuple(self.skipped_embeddings.keys())) if self.previously_displayed_embeddings != displayed_embeddings: self.previously_displayed_embeddings = displayed_embeddings diff --git a/modules/ui.py b/modules/ui.py index d2397bb26..475d6c889 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1038,7 +1038,7 @@ def create_ui(): with gr.Column(elem_id='ti_gallery_container'): ti_output = gr.Text(elem_id="ti_output", value="", show_label=False) - _ti_gallery = gr.Gallery(label='Output', show_label=False, elem_id='ti_gallery').style(grid=4) + _ti_gallery = gr.Gallery(label='Output', show_label=False, elem_id='ti_gallery').style(columns=4) _ti_progress = gr.HTML(elem_id="ti_progress", value="") ti_outcome = gr.HTML(elem_id="ti_error", value="") diff --git a/modules/ui_components.py b/modules/ui_components.py index 2b1da2cb2..6d2186b92 100644 --- a/modules/ui_components.py +++ b/modules/ui_components.py @@ -62,3 +62,12 @@ class DropdownMulti(FormComponent, gr.Dropdown): def get_block_name(self): return "dropdown" + + +class DropdownEditable(FormComponent, gr.Dropdown): + """Same as gr.Dropdown but allows editing value""" + def __init__(self, **kwargs): + super().__init__(allow_custom_value=True, **kwargs) + + def get_block_name(self): + return "dropdown" diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index acaf4df64..065f155a4 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -210,7 +210,7 @@ def create_ui(container, button, tabname): ui.tabname = tabname with gr.Tabs(elem_id=tabname+"_extra_tabs"): for page in ui.stored_extra_pages: - with gr.Tab(page.title): + with gr.Tab(page.title, id=page.title.lower().replace(" ", "_")): page_elem = gr.HTML(page.create_html(ui.tabname)) ui.pages.append(page_elem) _filter = gr.Textbox('', show_label=False, elem_id=tabname+"_extra_search", placeholder="Search...", visible=False) diff --git a/scripts/outpainting_mk_2.py b/scripts/outpainting_mk_2.py index 4e764fee4..d375ff764 100644 --- a/scripts/outpainting_mk_2.py +++ b/scripts/outpainting_mk_2.py @@ -278,6 +278,6 @@ class Script(scripts.Script): images.save_image(img, p.outpath_samples, "", res.seed, p.prompt, opts.samples_format, info=res.info, p=p) if opts.grid_save and not unwanted_grid_because_of_img_count: - images.save_image(combined_grid_image, p.outpath_grids, "grid", res.seed, p.prompt, opts.grid_format, info=res.info, short_filename=not opts.grid_extended_filename, grid=True, p=p) + images.save_image(combined_grid_image, p.outpath_grids, "grid", res.seed, p.prompt, opts.samples_format, info=res.info, short_filename=not opts.grid_extended_filename, grid=True, p=p) return res