mirror of
https://git.datalinker.icu/comfyanonymous/ComfyUI
synced 2026-09-07 15:17:04 +08:00
Make controls input work for Recraft Image to Image node (#120)
This commit is contained in:
parent
eca1467fd9
commit
96753dc180
@ -180,10 +180,14 @@ class ApiClient:
|
|||||||
data: Dict[str, Any],
|
data: Dict[str, Any],
|
||||||
files: Dict[str, Any],
|
files: Dict[str, Any],
|
||||||
headers: Optional[Dict[str, str]] = None,
|
headers: Optional[Dict[str, str]] = None,
|
||||||
|
multipart_parser = None,
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
if headers and "Content-Type" in headers:
|
if headers and "Content-Type" in headers:
|
||||||
del headers["Content-Type"]
|
del headers["Content-Type"]
|
||||||
|
|
||||||
|
if multipart_parser:
|
||||||
|
data = multipart_parser(data)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"data": data,
|
"data": data,
|
||||||
"files": files,
|
"files": files,
|
||||||
@ -222,6 +226,7 @@ class ApiClient:
|
|||||||
files: Optional[Dict[str, Any]] = None,
|
files: Optional[Dict[str, Any]] = None,
|
||||||
headers: Optional[Dict[str, str]] = None,
|
headers: Optional[Dict[str, str]] = None,
|
||||||
content_type: str = "application/json",
|
content_type: str = "application/json",
|
||||||
|
multipart_parser: Callable = None,
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Make an HTTP request to the API
|
Make an HTTP request to the API
|
||||||
@ -261,7 +266,7 @@ class ApiClient:
|
|||||||
case "application/x-www-form-urlencoded":
|
case "application/x-www-form-urlencoded":
|
||||||
payload_args = self._create_urlencoded_form_data_args(data, request_headers)
|
payload_args = self._create_urlencoded_form_data_args(data, request_headers)
|
||||||
case "multipart/form-data":
|
case "multipart/form-data":
|
||||||
payload_args = self._create_form_data_args(data, files, request_headers)
|
payload_args = self._create_form_data_args(data, files, request_headers, multipart_parser)
|
||||||
case _:
|
case _:
|
||||||
payload_args = self._create_json_payload_args(data, request_headers)
|
payload_args = self._create_json_payload_args(data, request_headers)
|
||||||
|
|
||||||
@ -400,6 +405,7 @@ class SynchronousOperation(Generic[T, R]):
|
|||||||
timeout: float = 604800.0,
|
timeout: float = 604800.0,
|
||||||
verify_ssl: bool = True,
|
verify_ssl: bool = True,
|
||||||
content_type: str = "application/json",
|
content_type: str = "application/json",
|
||||||
|
multipart_parser: Callable = None,
|
||||||
):
|
):
|
||||||
self.endpoint = endpoint
|
self.endpoint = endpoint
|
||||||
self.request = request
|
self.request = request
|
||||||
@ -411,6 +417,7 @@ class SynchronousOperation(Generic[T, R]):
|
|||||||
self.verify_ssl = verify_ssl
|
self.verify_ssl = verify_ssl
|
||||||
self.files = files
|
self.files = files
|
||||||
self.content_type = content_type
|
self.content_type = content_type
|
||||||
|
self.multipart_parser = multipart_parser
|
||||||
def execute(self, client: Optional[ApiClient] = None) -> R:
|
def execute(self, client: Optional[ApiClient] = None) -> R:
|
||||||
"""Execute the API operation using the provided client or create one"""
|
"""Execute the API operation using the provided client or create one"""
|
||||||
try:
|
try:
|
||||||
@ -454,6 +461,7 @@ class SynchronousOperation(Generic[T, R]):
|
|||||||
params=self.endpoint.query_params,
|
params=self.endpoint.query_params,
|
||||||
files=self.files,
|
files=self.files,
|
||||||
content_type=self.content_type,
|
content_type=self.content_type,
|
||||||
|
multipart_parser=self.multipart_parser
|
||||||
)
|
)
|
||||||
|
|
||||||
# Debug log for response
|
# Debug log for response
|
||||||
|
|||||||
@ -66,6 +66,7 @@ def handle_recraft_file_request(
|
|||||||
files=files,
|
files=files,
|
||||||
content_type="multipart/form-data",
|
content_type="multipart/form-data",
|
||||||
auth_token=auth_token,
|
auth_token=auth_token,
|
||||||
|
multipart_parser=recraft_multipart_parser,
|
||||||
)
|
)
|
||||||
response: RecraftImageGenerationResponse = operation.execute()
|
response: RecraftImageGenerationResponse = operation.execute()
|
||||||
all_bytesio = []
|
all_bytesio = []
|
||||||
@ -78,6 +79,72 @@ def handle_recraft_file_request(
|
|||||||
return all_bytesio
|
return all_bytesio
|
||||||
|
|
||||||
|
|
||||||
|
def recraft_multipart_parser(data, parent_key=None, formatter: callable=None, converted_to_check: list[list]=None, is_list=False) -> dict:
|
||||||
|
"""
|
||||||
|
Formats data such that multipart/form-data will work with requests library
|
||||||
|
when both files and data are present.
|
||||||
|
|
||||||
|
The OpenAI client that Recraft uses has a bizarre way of serializing lists:
|
||||||
|
|
||||||
|
It does NOT keep track of indeces of each list, so for background_color, that must be serialized as:
|
||||||
|
'background_color[rgb][]' = [0, 0, 255]
|
||||||
|
where the array is assigned to a key that has '[]' at the end, to signal it's an array.
|
||||||
|
|
||||||
|
This has the consequence of nested lists having the exact same key, forcing arrays to merge; all colors inputs fall under the same key:
|
||||||
|
if 1 color -> 'controls[colors][][rgb][]' = [0, 0, 255]
|
||||||
|
if 2 colors -> 'controls[colors][][rgb][]' = [0, 0, 255, 255, 0, 0]
|
||||||
|
if 3 colors -> 'controls[colors][][rgb][]' = [0, 0, 255, 255, 0, 0, 0, 255, 0]
|
||||||
|
etc.
|
||||||
|
Whoever made this serialization up at OpenAI added the constraint that lists must be of uniform length on objects of same 'type'.
|
||||||
|
"""
|
||||||
|
# Modification of a function that handled a different type of multipart parsing, big ups:
|
||||||
|
# https://gist.github.com/kazqvaizer/4cebebe5db654a414132809f9f88067b
|
||||||
|
|
||||||
|
def handle_converted_lists(data, parent_key, lists_to_check=tuple[list]):
|
||||||
|
# if list already exists exists, just extend list with data
|
||||||
|
for check_list in lists_to_check:
|
||||||
|
for conv_tuple in check_list:
|
||||||
|
if conv_tuple[0] == parent_key and type(conv_tuple[1]) is list:
|
||||||
|
conv_tuple[1].append(formatter(data))
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
if converted_to_check is None:
|
||||||
|
converted_to_check = []
|
||||||
|
|
||||||
|
|
||||||
|
if formatter is None:
|
||||||
|
formatter = lambda v: v # Multipart representation of value
|
||||||
|
|
||||||
|
if type(data) is not dict:
|
||||||
|
# if list already exists exists, just extend list with data
|
||||||
|
added = handle_converted_lists(data, parent_key, converted_to_check)
|
||||||
|
if added:
|
||||||
|
return {}
|
||||||
|
# otherwise if is_list, create new list with data
|
||||||
|
if is_list:
|
||||||
|
return {parent_key: [formatter(data)]}
|
||||||
|
# return new key with data
|
||||||
|
return {parent_key: formatter(data)}
|
||||||
|
|
||||||
|
converted = []
|
||||||
|
next_check = [converted]
|
||||||
|
next_check.extend(converted_to_check)
|
||||||
|
|
||||||
|
for key, value in data.items():
|
||||||
|
current_key = key if parent_key is None else f"{parent_key}[{key}]"
|
||||||
|
if type(value) is dict:
|
||||||
|
converted.extend(recraft_multipart_parser(value, current_key, formatter, next_check).items())
|
||||||
|
elif type(value) is list:
|
||||||
|
for ind, list_value in enumerate(value):
|
||||||
|
iter_key = f"{current_key}[]"
|
||||||
|
converted.extend(recraft_multipart_parser(list_value, iter_key, formatter, next_check, is_list=True).items())
|
||||||
|
else:
|
||||||
|
converted.append((current_key, formatter(value)))
|
||||||
|
|
||||||
|
return dict(converted)
|
||||||
|
|
||||||
|
|
||||||
class SVG:
|
class SVG:
|
||||||
"""
|
"""
|
||||||
Stores SVG representations via a list of BytesIO objects.
|
Stores SVG representations via a list of BytesIO objects.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user