mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-03-24 18:43:36 +08:00
292 lines
11 KiB
Python
292 lines
11 KiB
Python
from io import BytesIO
|
|
|
|
from typing_extensions import override
|
|
|
|
from comfy_api.latest import IO, ComfyExtension
|
|
from comfy_api_nodes.apis.quiver import (
|
|
QuiverImageObject,
|
|
QuiverImageToSVGRequest,
|
|
QuiverSVGResponse,
|
|
QuiverTextToSVGRequest,
|
|
)
|
|
from comfy_api_nodes.util import (
|
|
ApiEndpoint,
|
|
sync_op,
|
|
upload_image_to_comfyapi,
|
|
validate_string,
|
|
)
|
|
from comfy_extras.nodes_images import SVG
|
|
|
|
|
|
class QuiverTextToSVGNode(IO.ComfyNode):
|
|
@classmethod
|
|
def define_schema(cls):
|
|
return IO.Schema(
|
|
node_id="QuiverTextToSVGNode",
|
|
display_name="Quiver Text to SVG",
|
|
category="api node/image/Quiver",
|
|
description="Generate an SVG from a text prompt using Quiver AI.",
|
|
inputs=[
|
|
IO.String.Input(
|
|
"prompt",
|
|
multiline=True,
|
|
default="",
|
|
tooltip="Text description of the desired SVG output.",
|
|
),
|
|
IO.String.Input(
|
|
"instructions",
|
|
multiline=True,
|
|
default="",
|
|
tooltip="Additional style or formatting guidance.",
|
|
optional=True,
|
|
),
|
|
IO.Autogrow.Input(
|
|
"reference_images",
|
|
template=IO.Autogrow.TemplatePrefix(
|
|
IO.Image.Input("image"),
|
|
prefix="ref_",
|
|
min=0,
|
|
max=4,
|
|
),
|
|
tooltip="Up to 4 reference images to guide the generation.",
|
|
optional=True,
|
|
),
|
|
IO.DynamicCombo.Input(
|
|
"model",
|
|
options=[
|
|
IO.DynamicCombo.Option(
|
|
"arrow-preview",
|
|
[
|
|
IO.Float.Input(
|
|
"temperature",
|
|
default=1.0,
|
|
min=0.0,
|
|
max=2.0,
|
|
step=0.1,
|
|
display_mode=IO.NumberDisplay.slider,
|
|
tooltip="Randomness control. Higher values increase randomness.",
|
|
advanced=True,
|
|
),
|
|
IO.Float.Input(
|
|
"top_p",
|
|
default=1.0,
|
|
min=0.05,
|
|
max=1.0,
|
|
step=0.05,
|
|
display_mode=IO.NumberDisplay.slider,
|
|
tooltip="Nucleus sampling parameter.",
|
|
advanced=True,
|
|
),
|
|
IO.Float.Input(
|
|
"presence_penalty",
|
|
default=0.0,
|
|
min=-2.0,
|
|
max=2.0,
|
|
step=0.1,
|
|
display_mode=IO.NumberDisplay.slider,
|
|
tooltip="Token presence penalty.",
|
|
advanced=True,
|
|
),
|
|
],
|
|
),
|
|
],
|
|
tooltip="Model to use for SVG generation.",
|
|
),
|
|
IO.Int.Input(
|
|
"seed",
|
|
default=0,
|
|
min=0,
|
|
max=2147483647,
|
|
control_after_generate=True,
|
|
tooltip="Seed to determine if node should re-run; "
|
|
"actual results are nondeterministic regardless of seed.",
|
|
),
|
|
],
|
|
outputs=[
|
|
IO.SVG.Output(),
|
|
],
|
|
hidden=[
|
|
IO.Hidden.auth_token_comfy_org,
|
|
IO.Hidden.api_key_comfy_org,
|
|
IO.Hidden.unique_id,
|
|
],
|
|
is_api_node=True,
|
|
price_badge=IO.PriceBadge(
|
|
expr="""{"type":"usd","usd":0.429}""",
|
|
),
|
|
)
|
|
|
|
@classmethod
|
|
async def execute(
|
|
cls,
|
|
prompt: str,
|
|
model: dict,
|
|
seed: int,
|
|
instructions: str = None,
|
|
reference_images: IO.Autogrow.Type = None,
|
|
) -> IO.NodeOutput:
|
|
validate_string(prompt, strip_whitespace=False, min_length=1)
|
|
|
|
references = None
|
|
if reference_images:
|
|
references = []
|
|
for key in reference_images:
|
|
url = await upload_image_to_comfyapi(cls, reference_images[key])
|
|
references.append(QuiverImageObject(url=url))
|
|
if len(references) > 4:
|
|
raise ValueError("Maximum 4 reference images are allowed.")
|
|
|
|
instructions_val = instructions.strip() if instructions else None
|
|
if instructions_val == "":
|
|
instructions_val = None
|
|
|
|
response = await sync_op(
|
|
cls,
|
|
ApiEndpoint(path="/proxy/quiver/v1/svgs/generations", method="POST"),
|
|
response_model=QuiverSVGResponse,
|
|
data=QuiverTextToSVGRequest(
|
|
model=model["model"],
|
|
prompt=prompt,
|
|
instructions=instructions_val,
|
|
references=references,
|
|
temperature=model.get("temperature"),
|
|
top_p=model.get("top_p"),
|
|
presence_penalty=model.get("presence_penalty"),
|
|
),
|
|
)
|
|
|
|
svg_data = [BytesIO(item.svg.encode("utf-8")) for item in response.data]
|
|
return IO.NodeOutput(SVG(svg_data))
|
|
|
|
|
|
class QuiverImageToSVGNode(IO.ComfyNode):
|
|
@classmethod
|
|
def define_schema(cls):
|
|
return IO.Schema(
|
|
node_id="QuiverImageToSVGNode",
|
|
display_name="Quiver Image to SVG",
|
|
category="api node/image/Quiver",
|
|
description="Vectorize a raster image into SVG using Quiver AI.",
|
|
inputs=[
|
|
IO.Image.Input(
|
|
"image",
|
|
tooltip="Input image to vectorize.",
|
|
),
|
|
IO.Boolean.Input(
|
|
"auto_crop",
|
|
default=False,
|
|
tooltip="Automatically crop to the dominant subject.",
|
|
),
|
|
IO.DynamicCombo.Input(
|
|
"model",
|
|
options=[
|
|
IO.DynamicCombo.Option(
|
|
"arrow-preview",
|
|
[
|
|
IO.Int.Input(
|
|
"target_size",
|
|
default=1024,
|
|
min=128,
|
|
max=4096,
|
|
tooltip="Square resize target in pixels.",
|
|
),
|
|
IO.Float.Input(
|
|
"temperature",
|
|
default=1.0,
|
|
min=0.0,
|
|
max=2.0,
|
|
step=0.1,
|
|
display_mode=IO.NumberDisplay.slider,
|
|
tooltip="Randomness control. Higher values increase randomness.",
|
|
advanced=True,
|
|
),
|
|
IO.Float.Input(
|
|
"top_p",
|
|
default=1.0,
|
|
min=0.05,
|
|
max=1.0,
|
|
step=0.05,
|
|
display_mode=IO.NumberDisplay.slider,
|
|
tooltip="Nucleus sampling parameter.",
|
|
advanced=True,
|
|
),
|
|
IO.Float.Input(
|
|
"presence_penalty",
|
|
default=0.0,
|
|
min=-2.0,
|
|
max=2.0,
|
|
step=0.1,
|
|
display_mode=IO.NumberDisplay.slider,
|
|
tooltip="Token presence penalty.",
|
|
advanced=True,
|
|
),
|
|
],
|
|
),
|
|
],
|
|
tooltip="Model to use for SVG vectorization.",
|
|
),
|
|
IO.Int.Input(
|
|
"seed",
|
|
default=0,
|
|
min=0,
|
|
max=2147483647,
|
|
control_after_generate=True,
|
|
tooltip="Seed to determine if node should re-run; "
|
|
"actual results are nondeterministic regardless of seed.",
|
|
),
|
|
],
|
|
outputs=[
|
|
IO.SVG.Output(),
|
|
],
|
|
hidden=[
|
|
IO.Hidden.auth_token_comfy_org,
|
|
IO.Hidden.api_key_comfy_org,
|
|
IO.Hidden.unique_id,
|
|
],
|
|
is_api_node=True,
|
|
price_badge=IO.PriceBadge(
|
|
expr="""{"type":"usd","usd":0.429}""",
|
|
),
|
|
)
|
|
|
|
@classmethod
|
|
async def execute(
|
|
cls,
|
|
image,
|
|
auto_crop: bool,
|
|
model: dict,
|
|
seed: int,
|
|
) -> IO.NodeOutput:
|
|
image_url = await upload_image_to_comfyapi(cls, image)
|
|
|
|
response = await sync_op(
|
|
cls,
|
|
ApiEndpoint(path="/proxy/quiver/v1/svgs/vectorizations", method="POST"),
|
|
response_model=QuiverSVGResponse,
|
|
data=QuiverImageToSVGRequest(
|
|
model=model["model"],
|
|
image=QuiverImageObject(url=image_url),
|
|
auto_crop=auto_crop if auto_crop else None,
|
|
target_size=model.get("target_size"),
|
|
temperature=model.get("temperature"),
|
|
top_p=model.get("top_p"),
|
|
presence_penalty=model.get("presence_penalty"),
|
|
),
|
|
)
|
|
|
|
svg_data = [BytesIO(item.svg.encode("utf-8")) for item in response.data]
|
|
return IO.NodeOutput(SVG(svg_data))
|
|
|
|
|
|
class QuiverExtension(ComfyExtension):
|
|
@override
|
|
async def get_node_list(self) -> list[type[IO.ComfyNode]]:
|
|
return [
|
|
QuiverTextToSVGNode,
|
|
QuiverImageToSVGNode,
|
|
]
|
|
|
|
|
|
async def comfy_entrypoint() -> QuiverExtension:
|
|
return QuiverExtension()
|