mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-05-10 01:02:56 +08:00
feat(api-nodes): replace NanoBanana2 node with new node that uses Autogrow
Some checks failed
Python Linting / Run Ruff (push) Has been cancelled
Python Linting / Run Pylint (push) Has been cancelled
Build package / Build Test (3.10) (push) Has been cancelled
Build package / Build Test (3.11) (push) Has been cancelled
Build package / Build Test (3.12) (push) Has been cancelled
Build package / Build Test (3.13) (push) Has been cancelled
Build package / Build Test (3.14) (push) Has been cancelled
Some checks failed
Python Linting / Run Ruff (push) Has been cancelled
Python Linting / Run Pylint (push) Has been cancelled
Build package / Build Test (3.10) (push) Has been cancelled
Build package / Build Test (3.11) (push) Has been cancelled
Build package / Build Test (3.12) (push) Has been cancelled
Build package / Build Test (3.13) (push) Has been cancelled
Build package / Build Test (3.14) (push) Has been cancelled
This commit is contained in:
parent
9c34f5f36a
commit
500184918d
@ -78,7 +78,7 @@ class NodeReplaceManager:
|
|||||||
for input_map in replacement.input_mapping:
|
for input_map in replacement.input_mapping:
|
||||||
if "set_value" in input_map:
|
if "set_value" in input_map:
|
||||||
new_node_struct["inputs"][input_map["new_id"]] = input_map["set_value"]
|
new_node_struct["inputs"][input_map["new_id"]] = input_map["set_value"]
|
||||||
elif "old_id" in input_map:
|
elif "old_id" in input_map and input_map["old_id"] in node_struct["inputs"]:
|
||||||
new_node_struct["inputs"][input_map["new_id"]] = node_struct["inputs"][input_map["old_id"]]
|
new_node_struct["inputs"][input_map["new_id"]] = node_struct["inputs"][input_map["old_id"]]
|
||||||
# finalize input replacement
|
# finalize input replacement
|
||||||
prompt[node_number] = new_node_struct
|
prompt[node_number] = new_node_struct
|
||||||
|
|||||||
@ -83,13 +83,16 @@ class GeminiImageModel(str, Enum):
|
|||||||
|
|
||||||
async def create_image_parts(
|
async def create_image_parts(
|
||||||
cls: type[IO.ComfyNode],
|
cls: type[IO.ComfyNode],
|
||||||
images: Input.Image,
|
images: Input.Image | list[Input.Image],
|
||||||
image_limit: int = 0,
|
image_limit: int = 0,
|
||||||
) -> list[GeminiPart]:
|
) -> list[GeminiPart]:
|
||||||
image_parts: list[GeminiPart] = []
|
image_parts: list[GeminiPart] = []
|
||||||
if image_limit < 0:
|
if image_limit < 0:
|
||||||
raise ValueError("image_limit must be greater than or equal to 0 when creating Gemini image parts.")
|
raise ValueError("image_limit must be greater than or equal to 0 when creating Gemini image parts.")
|
||||||
total_images = get_number_of_images(images)
|
|
||||||
|
# Accept either a single (possibly-batched) tensor or a list of them; share URL budget across all.
|
||||||
|
images_list: list[Input.Image] = images if isinstance(images, list) else [images]
|
||||||
|
total_images = sum(get_number_of_images(img) for img in images_list)
|
||||||
if total_images <= 0:
|
if total_images <= 0:
|
||||||
raise ValueError("No images provided to create_image_parts; at least one image is required.")
|
raise ValueError("No images provided to create_image_parts; at least one image is required.")
|
||||||
|
|
||||||
@ -100,7 +103,7 @@ async def create_image_parts(
|
|||||||
num_url_images = min(effective_max, 10) # Vertex API max number of image links
|
num_url_images = min(effective_max, 10) # Vertex API max number of image links
|
||||||
reference_images_urls = await upload_images_to_comfyapi(
|
reference_images_urls = await upload_images_to_comfyapi(
|
||||||
cls,
|
cls,
|
||||||
images,
|
images_list,
|
||||||
max_images=num_url_images,
|
max_images=num_url_images,
|
||||||
)
|
)
|
||||||
for reference_image_url in reference_images_urls:
|
for reference_image_url in reference_images_urls:
|
||||||
@ -112,15 +115,22 @@ async def create_image_parts(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
for idx in range(num_url_images, effective_max):
|
if effective_max > num_url_images:
|
||||||
image_parts.append(
|
flat: list[torch.Tensor] = []
|
||||||
GeminiPart(
|
for tensor in images_list:
|
||||||
inlineData=GeminiInlineData(
|
if len(tensor.shape) == 4:
|
||||||
mimeType=GeminiMimeType.image_png,
|
flat.extend(tensor[i] for i in range(tensor.shape[0]))
|
||||||
data=tensor_to_base64_string(images[idx]),
|
else:
|
||||||
|
flat.append(tensor)
|
||||||
|
for idx in range(num_url_images, effective_max):
|
||||||
|
image_parts.append(
|
||||||
|
GeminiPart(
|
||||||
|
inlineData=GeminiInlineData(
|
||||||
|
mimeType=GeminiMimeType.image_png,
|
||||||
|
data=tensor_to_base64_string(flat[idx]),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
return image_parts
|
return image_parts
|
||||||
|
|
||||||
|
|
||||||
@ -849,7 +859,7 @@ class GeminiNanoBanana2(IO.ComfyNode):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def define_schema(cls):
|
def define_schema(cls):
|
||||||
return IO.Schema(
|
return IO.Schema(
|
||||||
node_id="GeminiNanoBanana2",
|
node_id="GeminiNanoBanana2V2",
|
||||||
display_name="Nano Banana 2",
|
display_name="Nano Banana 2",
|
||||||
category="api node/image/Gemini",
|
category="api node/image/Gemini",
|
||||||
description="Generate or edit images synchronously via Google Vertex API.",
|
description="Generate or edit images synchronously via Google Vertex API.",
|
||||||
@ -919,11 +929,14 @@ class GeminiNanoBanana2(IO.ComfyNode):
|
|||||||
"thinking_level",
|
"thinking_level",
|
||||||
options=["MINIMAL", "HIGH"],
|
options=["MINIMAL", "HIGH"],
|
||||||
),
|
),
|
||||||
IO.Image.Input(
|
IO.Autogrow.Input(
|
||||||
"images",
|
"images",
|
||||||
optional=True,
|
template=IO.Autogrow.TemplateNames(
|
||||||
tooltip="Optional reference image(s). "
|
IO.Image.Input("image"),
|
||||||
"To include multiple images, use the Batch Images node (up to 14).",
|
names=[f"image_{i}" for i in range(1, 15)],
|
||||||
|
min=0,
|
||||||
|
),
|
||||||
|
tooltip="Optional reference image(s). Up to 14 images total.",
|
||||||
),
|
),
|
||||||
IO.Custom("GEMINI_INPUT_FILES").Input(
|
IO.Custom("GEMINI_INPUT_FILES").Input(
|
||||||
"files",
|
"files",
|
||||||
@ -968,7 +981,7 @@ class GeminiNanoBanana2(IO.ComfyNode):
|
|||||||
resolution: str,
|
resolution: str,
|
||||||
response_modalities: str,
|
response_modalities: str,
|
||||||
thinking_level: str,
|
thinking_level: str,
|
||||||
images: Input.Image | None = None,
|
images: IO.Autogrow.Type | None = None,
|
||||||
files: list[GeminiPart] | None = None,
|
files: list[GeminiPart] | None = None,
|
||||||
system_prompt: str = "",
|
system_prompt: str = "",
|
||||||
) -> IO.NodeOutput:
|
) -> IO.NodeOutput:
|
||||||
@ -977,10 +990,12 @@ class GeminiNanoBanana2(IO.ComfyNode):
|
|||||||
model = "gemini-3.1-flash-image-preview"
|
model = "gemini-3.1-flash-image-preview"
|
||||||
|
|
||||||
parts: list[GeminiPart] = [GeminiPart(text=prompt)]
|
parts: list[GeminiPart] = [GeminiPart(text=prompt)]
|
||||||
if images is not None:
|
if images:
|
||||||
if get_number_of_images(images) > 14:
|
image_tensors: list[Input.Image] = [t for t in images.values() if t is not None]
|
||||||
raise ValueError("The current maximum number of supported images is 14.")
|
if image_tensors:
|
||||||
parts.extend(await create_image_parts(cls, images))
|
if sum(get_number_of_images(t) for t in image_tensors) > 14:
|
||||||
|
raise ValueError("The current maximum number of supported images is 14.")
|
||||||
|
parts.extend(await create_image_parts(cls, image_tensors))
|
||||||
if files is not None:
|
if files is not None:
|
||||||
parts.extend(files)
|
parts.extend(files)
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@ async def register_replacements():
|
|||||||
await register_replacements_preview3d()
|
await register_replacements_preview3d()
|
||||||
await register_replacements_svdimg2vid()
|
await register_replacements_svdimg2vid()
|
||||||
await register_replacements_conditioningavg()
|
await register_replacements_conditioningavg()
|
||||||
|
await register_replacements_nanobanana2()
|
||||||
|
|
||||||
async def register_replacements_longeredge():
|
async def register_replacements_longeredge():
|
||||||
# No dynamic inputs here
|
# No dynamic inputs here
|
||||||
@ -92,6 +93,35 @@ async def register_replacements_conditioningavg():
|
|||||||
old_node_id="ConditioningAverage ",
|
old_node_id="ConditioningAverage ",
|
||||||
))
|
))
|
||||||
|
|
||||||
|
async def register_replacements_nanobanana2():
|
||||||
|
# GeminiNanoBanana2 replaced by GeminiNanoBanana2V2, which uses Autogrow for the images input.
|
||||||
|
await api.node_replacement.register(io.NodeReplace(
|
||||||
|
new_node_id="GeminiNanoBanana2V2",
|
||||||
|
old_node_id="GeminiNanoBanana2",
|
||||||
|
old_widget_ids=[
|
||||||
|
"prompt",
|
||||||
|
"model",
|
||||||
|
"seed",
|
||||||
|
"aspect_ratio",
|
||||||
|
"resolution",
|
||||||
|
"response_modalities",
|
||||||
|
"thinking_level",
|
||||||
|
"system_prompt",
|
||||||
|
],
|
||||||
|
input_mapping=[
|
||||||
|
{"new_id": "prompt", "old_id": "prompt"},
|
||||||
|
{"new_id": "model", "old_id": "model"},
|
||||||
|
{"new_id": "seed", "old_id": "seed"},
|
||||||
|
{"new_id": "aspect_ratio", "old_id": "aspect_ratio"},
|
||||||
|
{"new_id": "resolution", "old_id": "resolution"},
|
||||||
|
{"new_id": "response_modalities", "old_id": "response_modalities"},
|
||||||
|
{"new_id": "thinking_level", "old_id": "thinking_level"},
|
||||||
|
{"new_id": "images.image_1", "old_id": "images"},
|
||||||
|
{"new_id": "files", "old_id": "files"},
|
||||||
|
{"new_id": "system_prompt", "old_id": "system_prompt"},
|
||||||
|
],
|
||||||
|
))
|
||||||
|
|
||||||
class NodeReplacementsExtension(ComfyExtension):
|
class NodeReplacementsExtension(ComfyExtension):
|
||||||
async def on_load(self) -> None:
|
async def on_load(self) -> None:
|
||||||
await register_replacements()
|
await register_replacements()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user