mirror of
https://git.datalinker.icu/comfyanonymous/ComfyUI
synced 2026-09-07 13:27:07 +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],
|
||||
files: Dict[str, Any],
|
||||
headers: Optional[Dict[str, str]] = None,
|
||||
multipart_parser = None,
|
||||
) -> Dict[str, Any]:
|
||||
if headers and "Content-Type" in headers:
|
||||
del headers["Content-Type"]
|
||||
|
||||
if multipart_parser:
|
||||
data = multipart_parser(data)
|
||||
|
||||
return {
|
||||
"data": data,
|
||||
"files": files,
|
||||
@ -222,6 +226,7 @@ class ApiClient:
|
||||
files: Optional[Dict[str, Any]] = None,
|
||||
headers: Optional[Dict[str, str]] = None,
|
||||
content_type: str = "application/json",
|
||||
multipart_parser: Callable = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Make an HTTP request to the API
|
||||
@ -261,7 +266,7 @@ class ApiClient:
|
||||
case "application/x-www-form-urlencoded":
|
||||
payload_args = self._create_urlencoded_form_data_args(data, request_headers)
|
||||
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 _:
|
||||
payload_args = self._create_json_payload_args(data, request_headers)
|
||||
|
||||
@ -400,6 +405,7 @@ class SynchronousOperation(Generic[T, R]):
|
||||
timeout: float = 604800.0,
|
||||
verify_ssl: bool = True,
|
||||
content_type: str = "application/json",
|
||||
multipart_parser: Callable = None,
|
||||
):
|
||||
self.endpoint = endpoint
|
||||
self.request = request
|
||||
@ -411,6 +417,7 @@ class SynchronousOperation(Generic[T, R]):
|
||||
self.verify_ssl = verify_ssl
|
||||
self.files = files
|
||||
self.content_type = content_type
|
||||
self.multipart_parser = multipart_parser
|
||||
def execute(self, client: Optional[ApiClient] = None) -> R:
|
||||
"""Execute the API operation using the provided client or create one"""
|
||||
try:
|
||||
@ -454,6 +461,7 @@ class SynchronousOperation(Generic[T, R]):
|
||||
params=self.endpoint.query_params,
|
||||
files=self.files,
|
||||
content_type=self.content_type,
|
||||
multipart_parser=self.multipart_parser
|
||||
)
|
||||
|
||||
# Debug log for response
|
||||
|
||||
@ -66,6 +66,7 @@ def handle_recraft_file_request(
|
||||
files=files,
|
||||
content_type="multipart/form-data",
|
||||
auth_token=auth_token,
|
||||
multipart_parser=recraft_multipart_parser,
|
||||
)
|
||||
response: RecraftImageGenerationResponse = operation.execute()
|
||||
all_bytesio = []
|
||||
@ -78,6 +79,72 @@ def handle_recraft_file_request(
|
||||
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:
|
||||
"""
|
||||
Stores SVG representations via a list of BytesIO objects.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user