From 57e7b0f233c70559f833833d7098a8a6156eb86a Mon Sep 17 00:00:00 2001 From: silveroxides Date: Mon, 26 Oct 2026 21:29:58 +0100 Subject: [PATCH] Support list inputs in image_inputs for batch execution --- gemini_nodes.py | 28 ++++++++++++++++++++++++---- interactions_nodes.py | 28 ++++++++++++++++++++++++---- tests/test_gemini_nodes.py | 17 ++++++++++------- 3 files changed, 58 insertions(+), 15 deletions(-) diff --git a/gemini_nodes.py b/gemini_nodes.py index e230579..fc85524 100644 --- a/gemini_nodes.py +++ b/gemini_nodes.py @@ -1663,8 +1663,14 @@ class SSL_GeminiTextPrompt(IO.ComfyNode): extracted_seeds = _extract_seed_list(seed) is_seed_list = extracted_seeds is not None is_config_list = _is_list_arg(config) + max_image_list_len = ( + max((len(v) for v in image_inputs.values() if _is_list_arg(v)), default=0) + if isinstance(image_inputs, dict) + else 0 + ) + is_image_list = max_image_list_len > 0 - is_batch = is_prompt_list or is_video_list or is_seed_list or is_config_list + is_batch = is_prompt_list or is_video_list or is_seed_list or is_config_list or is_image_list if not is_batch: return cls._execute_single( @@ -1689,6 +1695,7 @@ class SSL_GeminiTextPrompt(IO.ComfyNode): len(video_list) if is_video_list else 1, len(seed_list) if is_seed_list else 1, len(config_list) if is_config_list else 1, + max_image_list_len if is_image_list else 1, ) def _unwrap_scalar(val, default=None): @@ -1727,11 +1734,24 @@ class SSL_GeminiTextPrompt(IO.ComfyNode): img_size_val = str(_unwrap_scalar(image_size, "None")) resp_schema_val = _unwrap_scalar(response_schema, None) base_seed = int(seed_list[0]) if (is_seed_list and len(seed_list) > 0) else int(_unwrap_scalar(seed, 0)) - has_distinct_item_inputs = (is_prompt_list and len(prompt_list) > 1) or (is_video_list and len(video_list) > 1) + has_distinct_item_inputs = ( + (is_prompt_list and len(prompt_list) > 1) + or (is_video_list and len(video_list) > 1) + or (max_image_list_len > 1) + ) def make_item_kwargs(idx, prepared_imgs=None, precomp_vid=None): p = prompt_list[min(idx, len(prompt_list) - 1)] if prompt_list else "" v = video_list[min(idx, len(video_list) - 1)] if video_list else None + if isinstance(image_inputs, dict): + item_imgs = {} + for k, val in image_inputs.items(): + if _is_list_arg(val) and val: + item_imgs[k] = val[min(idx, len(val) - 1)] + else: + item_imgs[k] = val + else: + item_imgs = image_inputs s = (base_seed + idx) if (base_seed != 0 and batch_count > 1) else base_seed fp_seed = base_seed if has_distinct_item_inputs else s @@ -1768,7 +1788,7 @@ class SSL_GeminiTextPrompt(IO.ComfyNode): "timeout_fallback_text": timeout_fb_val, "image_size": img_size_val, "video": v, - "image_inputs": image_inputs, + "image_inputs": item_imgs, "response_schema": resp_schema_val, "prepared_images": prepared_imgs, "precomputed_video": precomp_vid, @@ -1787,7 +1807,7 @@ class SSL_GeminiTextPrompt(IO.ComfyNode): print(f"[INFO] Preparing sequential list execution for {batch_count} items (model: {model_val})") prepared_images = None - if image_inputs: + if image_inputs and max_image_list_len <= 1: try: prepared_images = cls._prepare_image_inputs(image_inputs) except Exception: diff --git a/interactions_nodes.py b/interactions_nodes.py index 95d0581..5cad565 100644 --- a/interactions_nodes.py +++ b/interactions_nodes.py @@ -1549,8 +1549,14 @@ class SSL_GeminiInteractionsTextPrompt(IO.ComfyNode): extracted_seeds = _extract_seed_list(seed) is_seed_list = extracted_seeds is not None is_config_list = _is_list_arg(config) + max_image_list_len = ( + max((len(v) for v in image_inputs.values() if _is_list_arg(v)), default=0) + if isinstance(image_inputs, dict) + else 0 + ) + is_image_list = max_image_list_len > 0 - is_batch = is_prompt_list or is_video_list or is_seed_list or is_config_list + is_batch = is_prompt_list or is_video_list or is_seed_list or is_config_list or is_image_list if not is_batch: return cls._execute_single( @@ -1576,6 +1582,7 @@ class SSL_GeminiInteractionsTextPrompt(IO.ComfyNode): len(video_list) if is_video_list else 1, len(seed_list) if is_seed_list else 1, len(config_list) if is_config_list else 1, + max_image_list_len if is_image_list else 1, ) def _unwrap_scalar(val, default=None): @@ -1616,11 +1623,24 @@ class SSL_GeminiInteractionsTextPrompt(IO.ComfyNode): resp_schema_val = _unwrap_scalar(response_schema, None) is_prev_id_list = _is_list_arg(previous_interaction_id) and len(previous_interaction_id) > 1 base_seed = int(seed_list[0]) if (is_seed_list and len(seed_list) > 0) else int(_unwrap_scalar(seed, 0)) - has_distinct_item_inputs = (is_prompt_list and len(prompt_list) > 1) or (is_video_list and len(video_list) > 1) + has_distinct_item_inputs = ( + (is_prompt_list and len(prompt_list) > 1) + or (is_video_list and len(video_list) > 1) + or (max_image_list_len > 1) + ) def make_item_kwargs(idx, chained_prev_id="", prepared_imgs=None, precomp_vid=None): p = prompt_list[min(idx, len(prompt_list) - 1)] if prompt_list else "" v = video_list[min(idx, len(video_list) - 1)] if video_list else None + if isinstance(image_inputs, dict): + item_imgs = {} + for k, val in image_inputs.items(): + if _is_list_arg(val) and val: + item_imgs[k] = val[min(idx, len(val) - 1)] + else: + item_imgs[k] = val + else: + item_imgs = image_inputs s = (base_seed + idx) if (base_seed != 0 and batch_count > 1) else base_seed fp_seed = base_seed if has_distinct_item_inputs else s @@ -1667,7 +1687,7 @@ class SSL_GeminiInteractionsTextPrompt(IO.ComfyNode): "store": eff_store, "previous_interaction_id": prev_id, "video": v, - "image_inputs": image_inputs, + "image_inputs": item_imgs, "response_schema": resp_schema_val, "prepared_images": prepared_imgs, "precomputed_video": precomp_vid, @@ -1688,7 +1708,7 @@ class SSL_GeminiInteractionsTextPrompt(IO.ComfyNode): print(f"[INFO] Preparing sequential list execution for {batch_count} items (model: {model_val})") prepared_images = None - if image_inputs and model_val not in cls.IMAGE_MODELS: + if image_inputs and max_image_list_len <= 1 and model_val not in cls.IMAGE_MODELS: try: prepared_images = cls._prepare_image_inputs(image_inputs) except Exception: diff --git a/tests/test_gemini_nodes.py b/tests/test_gemini_nodes.py index f5c01ce..e4d6a5d 100644 --- a/tests/test_gemini_nodes.py +++ b/tests/test_gemini_nodes.py @@ -1791,18 +1791,21 @@ def test_gemini_batch_single_list_element(structured_client): assert len(structured_client.calls) == 1 -def test_gemini_batch_videos(structured_client): +def test_gemini_batch_images_list(structured_client): structured_client.responses = [ - [("final", "Vid 1")], - [("final", "Vid 2")], + [("final", "Img Resp 1")], + [("final", "Img Resp 2")], ] args = _execute_kwargs(_config()) args.update( - prompt="Describe video", - video=[{"video": None, "fps": 1}, {"video": None, "fps": 1}], + prompt="Describe image", + image_inputs={ + "image_1": [torch.full((1, 4, 4, 3), 0.1), torch.full((1, 4, 4, 3), 0.2)], + "image_2": [torch.full((1, 4, 4, 3), 0.3), torch.full((1, 4, 4, 3), 0.4)], + }, ) output = gemini_nodes.SSL_GeminiTextPrompt.execute(**args) - assert isinstance(output[0], list) - assert output[0] == ["Vid 1", "Vid 2"] + assert isinstance(output[0], list) and len(output[0]) == 2 + assert output[0] == ["Img Resp 1", "Img Resp 2"] assert len(structured_client.calls) == 2