feat: add EagerEval dataclass for frontend-side node evaluation

Add EagerEval to the V3 API schema, enabling nodes to declare
frontend-evaluated JSONata expressions. The frontend uses this to
display computation results as badges without a backend round-trip.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
dante01yoon 2026-02-27 22:16:07 +09:00
parent 1f1ec377ce
commit 347802851f

View File

@ -1347,6 +1347,7 @@ class NodeInfoV1:
price_badge: dict | None = None
search_aliases: list[str]=None
essentials_category: str=None
eager_eval: dict | None = None
@dataclass
@ -1413,6 +1414,35 @@ class PriceBadge:
}
@dataclass
class EagerEval:
"""Frontend-evaluated expression badge for pure-computation nodes.
When declared, the frontend evaluates the JSONata expression against
widget values whenever they change, displaying the result as a badge
without a backend round-trip.
"""
expr: str | None = None
"""Static JSONata expression (e.g. "a + b")."""
expr_widget: str | None = None
"""Name of a widget whose value is the dynamic expression."""
engine: str = field(default="jsonata")
def validate(self) -> None:
if self.engine != "jsonata":
raise ValueError(f"Unsupported EagerEval.engine '{self.engine}'. Only 'jsonata' is supported.")
if not self.expr and not self.expr_widget:
raise ValueError("EagerEval requires either 'expr' or 'expr_widget'.")
def as_dict(self) -> dict[str, Any]:
d: dict[str, Any] = {"engine": self.engine}
if self.expr is not None:
d["expr"] = self.expr
if self.expr_widget is not None:
d["expr_widget"] = self.expr_widget
return d
@dataclass
class Schema:
"""Definition of V3 node properties."""
@ -1470,6 +1500,8 @@ class Schema:
"""When True, all inputs from the prompt will be passed to the node as kwargs, even if not defined in the schema."""
essentials_category: str | None = None
"""Optional category for the Essentials tab. Path-based like category field (e.g., 'Basic', 'Image Tools/Editing')."""
eager_eval: EagerEval | None = None
"""Optional frontend-evaluated expression badge for pure-computation nodes."""
def validate(self):
'''Validate the schema:
@ -1498,6 +1530,8 @@ class Schema:
output.validate()
if self.price_badge is not None:
self.price_badge.validate()
if self.eager_eval is not None:
self.eager_eval.validate()
def finalize(self):
"""Add hidden based on selected schema options, and give outputs without ids default ids."""
@ -1577,6 +1611,7 @@ class Schema:
price_badge=self.price_badge.as_dict(self.inputs) if self.price_badge is not None else None,
search_aliases=self.search_aliases if self.search_aliases else None,
essentials_category=self.essentials_category,
eager_eval=self.eager_eval.as_dict() if self.eager_eval is not None else None,
)
return info
@ -2225,6 +2260,7 @@ __all__ = [
"ImageCompare",
"PriceBadgeDepends",
"PriceBadge",
"EagerEval",
"BoundingBox",
"NodeReplace",
]