Skip to content

Commit 5123892

Browse files
authored
[Partner Nodes] feat(Quiver): add Arrow 2 models with reasoning effort to the SVG nodes (#16478)
Signed-off-by: bigcat88 <bigcat88@icloud.com>
1 parent b5cc883 commit 5123892

2 files changed

Lines changed: 89 additions & 33 deletions

File tree

‎comfy_api_nodes/apis/quiver.py‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ class QuiverImageObject(BaseModel):
66

77

88
class QuiverTextToSVGRequest(BaseModel):
9-
model: str = Field(default="arrow-preview")
9+
model: str = Field(...)
1010
prompt: str = Field(...)
11+
reasoning_effort: str | None = Field(default=None)
1112
instructions: str | None = Field(default=None)
1213
references: list[QuiverImageObject] | None = Field(default=None, max_length=4)
1314
temperature: float | None = Field(default=None, ge=0, le=2)
@@ -16,8 +17,9 @@ class QuiverTextToSVGRequest(BaseModel):
1617

1718

1819
class QuiverImageToSVGRequest(BaseModel):
19-
model: str = Field(default="arrow-preview")
20+
model: str = Field(...)
2021
image: QuiverImageObject = Field(...)
22+
reasoning_effort: str | None = Field(default=None)
2123
auto_crop: bool | None = Field(default=None)
2224
target_size: int | None = Field(default=None, ge=128, le=4096)
2325
temperature: float | None = Field(default=None, ge=0, le=2)

‎comfy_api_nodes/nodes_quiver.py‎

Lines changed: 85 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,78 @@
1717
)
1818
from comfy_extras.nodes_images import SVG
1919

20-
_ARROW_MODELS = ["arrow-1.1", "arrow-1.1-max", "arrow-preview"]
20+
_ARROW_MODELS = ["arrow-2", "arrow-2-telos", "arrow-1.1", "arrow-1.1-max", "arrow-preview"]
21+
_EFFORT_LEVELS = ["low", "medium", "high", "xhigh"]
22+
_TOKEN_MODEL_RATES = {"arrow-2": (4, 20), "arrow-2-telos": (6, 30)}
23+
_FIXED_GENERATION_USD = {"arrow-1.1": 0.286, "arrow-1.1-max": 0.3575, "arrow-preview": 0.429}
24+
_FIXED_VECTORIZATION_USD = {"arrow-1.1": 0.2145, "arrow-1.1-max": 0.286, "arrow-preview": 0.429}
25+
26+
_GENERATION_TOKENS = {
27+
"arrow-2": {
28+
"low": (713, 1100, 723, 20000),
29+
"medium": (713, 2200, 723, 34000),
30+
"high": (713, 2200, 723, 36000),
31+
"xhigh": (713, 2200, 723, 38000),
32+
},
33+
"arrow-2-telos": {
34+
"low": (476, 1100, 749, 36000),
35+
"medium": (476, 1300, 749, 67000),
36+
"high": (476, 1300, 749, 71000),
37+
"xhigh": (476, 10000, 749, 106000),
38+
},
39+
}
40+
_VECTORIZATION_TOKENS = {
41+
"arrow-2": {
42+
"low": (591, 600, 783, 23000),
43+
"medium": (591, 1300, 783, 36000),
44+
"high": (591, 1300, 783, 38000),
45+
"xhigh": (591, 1300, 783, 40000),
46+
},
47+
"arrow-2-telos": {
48+
"low": (394, 300, 522, 47000),
49+
"medium": (394, 300, 522, 48000),
50+
"high": (394, 300, 522, 101000),
51+
"xhigh": (394, 12000, 522, 115000),
52+
},
53+
}
54+
55+
56+
def _effort_bands(model, tokens):
57+
rate_in, rate_out = _TOKEN_MODEL_RATES[model]
58+
effort = "widgets.reasoning_effort"
59+
60+
def band(level):
61+
min_in, min_out, max_in, max_out = tokens[model][level]
62+
return (
63+
'{"type":"range_usd",'
64+
f'"min_usd":({min_in} * {rate_in} + {min_out} * {rate_out}) * 1.43 / 1000000,'
65+
f'"max_usd":({max_in} * {rate_in} + {max_out} * {rate_out}) * 1.43 / 1000000,'
66+
'"format":{"approximate":true}}'
67+
)
68+
69+
levels = [f'{effort} = "{level}" ? {band(level)}' for level in ("low", "medium", "xhigh")]
70+
return " : ".join([*levels, band("high")])
71+
72+
73+
def _arrow_price_badge(tokens, fixed_usd):
74+
branches = [f'widgets.model = "{model}" ? {{"type":"usd","usd":{usd}}}' for model, usd in fixed_usd.items()]
75+
branches.append(f'widgets.model = "arrow-2-telos" ? ({_effort_bands("arrow-2-telos", tokens)})')
76+
branches.append(f'({_effort_bands("arrow-2", tokens)})')
77+
return IO.PriceBadge(
78+
depends_on=IO.PriceBadgeDepends(widgets=["model", "reasoning_effort"]),
79+
expr="(" + " : ".join(branches) + ")",
80+
)
81+
82+
83+
def _reasoning_effort_input():
84+
return IO.Combo.Input(
85+
"reasoning_effort",
86+
options=_EFFORT_LEVELS,
87+
default="high",
88+
optional=True,
89+
tooltip="How much reasoning the model spends before drawing. Higher levels improve "
90+
"detail and cost more tokens. Only used by the Arrow 2 models.",
91+
)
2192

2293

2394
def _arrow_sampling_inputs():
@@ -104,6 +175,7 @@ def define_schema(cls):
104175
tooltip="Seed to determine if node should re-run; "
105176
"actual results are nondeterministic regardless of seed.",
106177
),
178+
_reasoning_effort_input(),
107179
],
108180
outputs=[
109181
IO.SVG.Output(),
@@ -114,18 +186,7 @@ def define_schema(cls):
114186
IO.Hidden.unique_id,
115187
],
116188
is_api_node=True,
117-
price_badge=IO.PriceBadge(
118-
depends_on=IO.PriceBadgeDepends(widgets=["model"]),
119-
expr="""
120-
(
121-
$contains(widgets.model, "max")
122-
? {"type":"usd","usd":0.3575}
123-
: $contains(widgets.model, "preview")
124-
? {"type":"usd","usd":0.429}
125-
: {"type":"usd","usd":0.286}
126-
)
127-
""",
128-
),
189+
price_badge=_arrow_price_badge(_GENERATION_TOKENS, _FIXED_GENERATION_USD),
129190
)
130191

131192
@classmethod
@@ -134,6 +195,7 @@ async def execute(
134195
prompt: str,
135196
model: dict,
136197
seed: int,
198+
reasoning_effort: str = None,
137199
instructions: str = None,
138200
reference_images: IO.Autogrow.Type = None,
139201
) -> IO.NodeOutput:
@@ -145,8 +207,6 @@ async def execute(
145207
for key in reference_images:
146208
url = await upload_image_to_comfyapi(cls, reference_images[key], mime_type="image/png")
147209
references.append(QuiverImageObject(url=url))
148-
if len(references) > 4:
149-
raise ValueError("Maximum 4 reference images are allowed.")
150210

151211
instructions_val = instructions.strip() if instructions else None
152212
if instructions_val == "":
@@ -158,6 +218,7 @@ async def execute(
158218
response_model=QuiverSVGResponse,
159219
data=QuiverTextToSVGRequest(
160220
model=model["model"],
221+
reasoning_effort=reasoning_effort if model["model"] in _TOKEN_MODEL_RATES else None,
161222
prompt=prompt,
162223
instructions=instructions_val,
163224
references=references,
@@ -198,10 +259,11 @@ def define_schema(cls):
198259
[
199260
IO.Int.Input(
200261
"target_size",
201-
default=1024,
202-
min=128,
262+
default=0,
263+
min=0,
203264
max=4096,
204-
tooltip="Square resize target in pixels.",
265+
tooltip="Square resize target in pixels, 128 to 4096. 0 keeps the source "
266+
"image size, which vectorizes more cleanly than forcing a resize.",
205267
advanced=True,
206268
),
207269
*_arrow_sampling_inputs(),
@@ -220,6 +282,7 @@ def define_schema(cls):
220282
tooltip="Seed to determine if node should re-run; "
221283
"actual results are nondeterministic regardless of seed.",
222284
),
285+
_reasoning_effort_input(),
223286
],
224287
outputs=[
225288
IO.SVG.Output(),
@@ -230,18 +293,7 @@ def define_schema(cls):
230293
IO.Hidden.unique_id,
231294
],
232295
is_api_node=True,
233-
price_badge=IO.PriceBadge(
234-
depends_on=IO.PriceBadgeDepends(widgets=["model"]),
235-
expr="""
236-
(
237-
$contains(widgets.model, "max")
238-
? {"type":"usd","usd":0.3575}
239-
: $contains(widgets.model, "preview")
240-
? {"type":"usd","usd":0.429}
241-
: {"type":"usd","usd":0.286}
242-
)
243-
""",
244-
),
296+
price_badge=_arrow_price_badge(_VECTORIZATION_TOKENS, _FIXED_VECTORIZATION_USD),
245297
)
246298

247299
@classmethod
@@ -251,6 +303,7 @@ async def execute(
251303
auto_crop: bool,
252304
model: dict,
253305
seed: int,
306+
reasoning_effort: str = None,
254307
) -> IO.NodeOutput:
255308
image_url = await upload_image_to_comfyapi(cls, image, mime_type="image/png")
256309

@@ -260,9 +313,10 @@ async def execute(
260313
response_model=QuiverSVGResponse,
261314
data=QuiverImageToSVGRequest(
262315
model=model["model"],
316+
reasoning_effort=reasoning_effort if model["model"] in _TOKEN_MODEL_RATES else None,
263317
image=QuiverImageObject(url=image_url),
264318
auto_crop=auto_crop if auto_crop else None,
265-
target_size=model.get("target_size"),
319+
target_size=model.get("target_size") or None,
266320
temperature=model.get("temperature"),
267321
top_p=model.get("top_p"),
268322
presence_penalty=model.get("presence_penalty"),

0 commit comments

Comments
 (0)