diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fb37f50c..7586ef63a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -100,6 +100,7 @@ But also many smaller quality-of-life improvements - for full details, see [Chan - handle missing preview image - ui connection monitor - kandinsky 5 t2i/i2i model type detection + - kanvas notify core on image size change ## Update for 2026-02-04 diff --git a/eslint.config.mjs b/eslint.config.mjs index c3af27afc..b5d6b5ee2 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -147,6 +147,7 @@ const jsConfig = defineConfig([ 'prefer-rest-params': 'off', 'prefer-template': 'warn', 'promise/no-nesting': 'off', + '@typescript-eslint/no-for-in-array': 'off', radix: 'off', '@stylistic/brace-style': [ 'error', diff --git a/extensions-builtin/sdnext-kanvas b/extensions-builtin/sdnext-kanvas index ddc9a52ed..d15b31206 160000 --- a/extensions-builtin/sdnext-kanvas +++ b/extensions-builtin/sdnext-kanvas @@ -1 +1 @@ -Subproject commit ddc9a52edd61ff7b903b15c3e2edf65fdacef9ac +Subproject commit d15b31206a581e49d0e8b70b375587c046e7f53f diff --git a/extensions-builtin/sdnext-modernui b/extensions-builtin/sdnext-modernui index c92d4e213..30a0c3977 160000 --- a/extensions-builtin/sdnext-modernui +++ b/extensions-builtin/sdnext-modernui @@ -1 +1 @@ -Subproject commit c92d4e213a5d03a1c49a9d8cf0f847da11953eb4 +Subproject commit 30a0c397762386b75dead5379d2e80063b5bf22f diff --git a/javascript/monitor.js b/javascript/monitor.js index c2905377b..bbce08d57 100644 --- a/javascript/monitor.js +++ b/javascript/monitor.js @@ -1,4 +1,5 @@ class ConnectionMonitorState { + static delay = 1000; static element; static version = ''; static commit = ''; @@ -58,14 +59,18 @@ async function wsMonitorLoop(url) { const ws = new WebSocket(`${url}/queue/join`); ws.onopen = () => {}; ws.onmessage = (evt) => updateIndicator(true); - ws.onclose = () => setTimeout(() => wsMonitorLoop(url), 10000); + ws.onclose = () => { + // happens regularly if there is no traffic + setTimeout(() => wsMonitorLoop(url), ConnectionMonitorState.delay); + }; ws.onerror = (e) => { + // actual error updateIndicator(false, {}, e.message); - setTimeout(() => monitorConnection(url), 10000); // eslint-disable-line no-use-before-define + setTimeout(() => wsMonitorLoop(url), ConnectionMonitorState.delay); }; } catch (e) { updateIndicator(false, {}, e.message); - setTimeout(() => monitorConnection(url), 10000); // eslint-disable-line no-use-before-define + setTimeout(monitorConnection, ConnectionMonitorState.delay); // eslint-disable-line no-use-before-define } } @@ -91,7 +96,7 @@ async function monitorConnection() { const url = res.url.split('/sdapi')[0].replace('https:', 'wss:').replace('http:', 'ws:'); // update global url as ws need fqdn wsMonitorLoop(url); } catch { - monitorConnection(false, data); - setTimeout(monitorConnection, 10000); + updateIndicator(false, data); + setTimeout(monitorConnection, ConnectionMonitorState.delay); } } diff --git a/modules/image/resize.py b/modules/image/resize.py index 73bbac9cd..1ff04b610 100644 --- a/modules/image/resize.py +++ b/modules/image/resize.py @@ -139,7 +139,9 @@ def resize_image(resize_mode: int, im: Image.Image | torch.Tensor, width: int, h if not isinstance(im, Image.Image): log.error(f'Resize image: image={type(im)} invalid type') return im - if (resize_mode == 0) or ((im.width == width) and (im.height == height)) or (width == 0 and height == 0): # none + if ((im.width == width) and (im.height == height)) or ((width == 0) and (height == 0)): # same + res = im.copy() + elif resize_mode == 0: # none res = im.copy() elif resize_mode == 1: # fixed res = resize(im, width, height) diff --git a/modules/ui_control.py b/modules/ui_control.py index 8a19b85c9..4301a49c5 100644 --- a/modules/ui_control.py +++ b/modules/ui_control.py @@ -101,13 +101,14 @@ def generate_click(job_id: str, state: str, active_tab: str, *args): shared.mem_mon.reset() jobid = shared.state.begin('Control') progress.start_task(job_id) + t = time.perf_counter() try: - t = time.perf_counter() for results in control_run(state, units, helpers.input_source, helpers.input_init, helpers.input_mask, active_tab, True, *args): progress.record_results(job_id, results) yield return_controls(results, t) except GeneratorExit: log.error("Control: generator exit") + return return_controls(results, t) except Exception as e: log.error(f"Control exception: {e}") errors.display(e, 'Control') diff --git a/modules/ui_control_helpers.py b/modules/ui_control_helpers.py index 45559bb7a..36f63d337 100644 --- a/modules/ui_control_helpers.py +++ b/modules/ui_control_helpers.py @@ -16,6 +16,7 @@ busy = False # used to synchronize select_input and generate_click input_source = None input_init = None input_mask = None +input_prev = None def initialize(): @@ -130,7 +131,7 @@ def process_kanvas(x): # only used when kanvas overrides gr.Image object def select_input(input_mode, input_image, init_image, init_type, input_video, input_batch, input_folder): - global busy, input_source, input_init, input_mask # pylint: disable=global-statement + global busy, input_source, input_init, input_mask, input_prev # pylint: disable=global-statement t0 = time.time() busy = False selected_input = input_image # default: Image or Kanvas @@ -140,10 +141,16 @@ def select_input(input_mode, input_image, init_image, init_type, input_video, in selected_input = input_batch elif input_mode == 'Folder': selected_input = input_folder + size = [gr.update(), gr.update()] if selected_input is None: + # log.debug(f'Select input: image={selected_input}') input_source = None return [gr.Tabs.update(), None, ''] + size + elif selected_input == input_prev: + # log.debug(f'Select input: image={selected_input} no change') + return [gr.Tabs.update(), None, ''] + size + input_prev = selected_input busy = True input_type = type(selected_input) diff --git a/modules/upscaler_algo.py b/modules/upscaler_algo.py index 21b7c30a6..e5df5611e 100644 --- a/modules/upscaler_algo.py +++ b/modules/upscaler_algo.py @@ -76,6 +76,7 @@ class UpscalerVIPS(Upscaler): log.debug(f"Upscale: name=VIPS input={img.size} output={upscaled.size} time={t1 - t0:.2f}") return upscaled + class UpscalerHQX(Upscaler): def __init__(self, dirname=None): # pylint: disable=unused-argument super().__init__(False) @@ -96,6 +97,7 @@ class UpscalerHQX(Upscaler): log.debug(f"Upscale: name=HQX input={img.size} output={upscaled.size} time={t1 - t0:.2f}") return upscaled + class UpscalerICBI(Upscaler): def __init__(self, dirname=None): # pylint: disable=unused-argument super().__init__(False) diff --git a/pipelines/model_google.py b/pipelines/model_google.py index 29d1f090e..b486463cc 100644 --- a/pipelines/model_google.py +++ b/pipelines/model_google.py @@ -2,7 +2,6 @@ import io import os import time from PIL import Image -from installer import install, reload from modules.logger import log @@ -27,6 +26,7 @@ aspect_ratios_buckets = { def google_requirements(): + from installer import install # , reload install('google-genai==1.52.0') # install('pydantic==2.11.7', ignore=True, quiet=True) # reload('pydantic', '2.11.7') diff --git a/scripts/postprocessing_upscale.py b/scripts/postprocessing_upscale.py index 948b3336d..b669f0e85 100644 --- a/scripts/postprocessing_upscale.py +++ b/scripts/postprocessing_upscale.py @@ -51,7 +51,7 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): def upscale(self, image, info, upscaler, upscale_mode, upscale_by, upscale_to_width, upscale_to_height, upscale_crop): if upscale_mode == 1: - upscale_by = max(upscale_to_width/image.width, upscale_to_height/image.height) + upscale_by = max(upscale_to_width / image.width, upscale_to_height / image.height) info["Postprocess upscale to"] = f"{upscale_to_width}x{upscale_to_height}" else: info["Postprocess upscale by"] = upscale_by