bizyengine 1.2.45__py3-none-any.whl → 1.2.71__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. bizyengine/bizy_server/errno.py +21 -0
  2. bizyengine/bizy_server/server.py +130 -160
  3. bizyengine/bizy_server/utils.py +3 -0
  4. bizyengine/bizyair_extras/__init__.py +38 -31
  5. bizyengine/bizyair_extras/third_party_api/__init__.py +15 -0
  6. bizyengine/bizyair_extras/third_party_api/nodes_doubao.py +535 -0
  7. bizyengine/bizyair_extras/third_party_api/nodes_flux.py +173 -0
  8. bizyengine/bizyair_extras/third_party_api/nodes_gemini.py +403 -0
  9. bizyengine/bizyair_extras/third_party_api/nodes_gpt.py +101 -0
  10. bizyengine/bizyair_extras/third_party_api/nodes_hailuo.py +115 -0
  11. bizyengine/bizyair_extras/third_party_api/nodes_kling.py +404 -0
  12. bizyengine/bizyair_extras/third_party_api/nodes_sora.py +218 -0
  13. bizyengine/bizyair_extras/third_party_api/nodes_veo3.py +193 -0
  14. bizyengine/bizyair_extras/third_party_api/nodes_wan_api.py +198 -0
  15. bizyengine/bizyair_extras/third_party_api/trd_nodes_base.py +183 -0
  16. bizyengine/bizyair_extras/utils/aliyun_oss.py +92 -0
  17. bizyengine/bizyair_extras/utils/audio.py +88 -0
  18. bizyengine/bizybot/__init__.py +12 -0
  19. bizyengine/bizybot/client.py +774 -0
  20. bizyengine/bizybot/config.py +129 -0
  21. bizyengine/bizybot/coordinator.py +556 -0
  22. bizyengine/bizybot/exceptions.py +186 -0
  23. bizyengine/bizybot/mcp/__init__.py +3 -0
  24. bizyengine/bizybot/mcp/manager.py +520 -0
  25. bizyengine/bizybot/mcp/models.py +46 -0
  26. bizyengine/bizybot/mcp/registry.py +129 -0
  27. bizyengine/bizybot/mcp/routing.py +378 -0
  28. bizyengine/bizybot/models.py +344 -0
  29. bizyengine/core/__init__.py +1 -0
  30. bizyengine/core/commands/servers/prompt_server.py +10 -1
  31. bizyengine/core/common/client.py +8 -7
  32. bizyengine/core/common/utils.py +30 -1
  33. bizyengine/core/image_utils.py +12 -283
  34. bizyengine/misc/llm.py +32 -15
  35. bizyengine/misc/utils.py +179 -2
  36. bizyengine/version.txt +1 -1
  37. {bizyengine-1.2.45.dist-info → bizyengine-1.2.71.dist-info}/METADATA +3 -1
  38. {bizyengine-1.2.45.dist-info → bizyengine-1.2.71.dist-info}/RECORD +40 -16
  39. {bizyengine-1.2.45.dist-info → bizyengine-1.2.71.dist-info}/WHEEL +0 -0
  40. {bizyengine-1.2.45.dist-info → bizyengine-1.2.71.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,173 @@
1
+ from bizyairsdk import tensor_to_bytesio
2
+
3
+ from .trd_nodes_base import BizyAirTrdApiBaseNode
4
+
5
+
6
+ class Flux_Kontext_API(BizyAirTrdApiBaseNode):
7
+ NODE_DISPLAY_NAME = "Flux Kontext API"
8
+ RETURN_TYPES = ("IMAGE",)
9
+ RETURN_NAMES = ("image",)
10
+ CATEGORY = "☁️BizyAir/External APIs/Flux"
11
+
12
+ @classmethod
13
+ def INPUT_TYPES(cls):
14
+ return {
15
+ "required": {
16
+ "prompt": (
17
+ "STRING",
18
+ {
19
+ "multiline": True,
20
+ "default": "",
21
+ },
22
+ ),
23
+ "model": (
24
+ ["flux-kontext-pro", "flux-kontext-max"],
25
+ {"default": "flux-kontext-pro"},
26
+ ),
27
+ "aspect_ratio": (
28
+ ["21:9", "16:9", "4:3", "1:1", "3:4", "9:16"],
29
+ {"default": "16:9"},
30
+ ),
31
+ "seed": ("INT", {"default": 0, "min": 0, "max": 2147483647}),
32
+ },
33
+ "optional": {
34
+ "image": ("IMAGE",),
35
+ },
36
+ }
37
+
38
+ def handle_inputs(self, headers, prompt_id, **kwargs):
39
+ prompt = kwargs.get("prompt", "")
40
+ image = kwargs.get("image", None)
41
+ model = kwargs.get("model", "flux-kontext-pro")
42
+ aspect_ratio = kwargs.get("aspect_ratio", "16:9")
43
+ seed = kwargs.get("seed", 0)
44
+ data = {
45
+ "prompt": prompt,
46
+ "model": model,
47
+ "aspect_ratio": aspect_ratio,
48
+ "seed": seed,
49
+ }
50
+ if image is not None:
51
+ image_url = self.upload_file(
52
+ tensor_to_bytesio(image=image, total_pixels=4096 * 4096),
53
+ f"{prompt_id}.png",
54
+ headers,
55
+ )
56
+ data["image"] = image_url
57
+ return data, "flux-kontext"
58
+
59
+ def handle_outputs(self, outputs):
60
+ images = self.combine_images(outputs[1])
61
+ return (images,)
62
+
63
+
64
+ class Flux_2_API(BizyAirTrdApiBaseNode):
65
+ NODE_DISPLAY_NAME = "Flux 2 API"
66
+ RETURN_TYPES = ("IMAGE",)
67
+ RETURN_NAMES = ("image",)
68
+ CATEGORY = "☁️BizyAir/External APIs/Flux"
69
+
70
+ @classmethod
71
+ def INPUT_TYPES(cls):
72
+ return {
73
+ "required": {
74
+ "prompt": (
75
+ "STRING",
76
+ {
77
+ "multiline": True,
78
+ "default": "",
79
+ },
80
+ ),
81
+ "model": (
82
+ ["flux-2-pro", "flux-2-flex"],
83
+ {"default": "flux-2-pro"},
84
+ ),
85
+ "seed": ("INT", {"default": 0, "min": 0, "max": 2147483647}),
86
+ "width": (
87
+ "INT",
88
+ {
89
+ "default": 1024,
90
+ "min": 0,
91
+ "step": 16,
92
+ "tooltip": "必须为16的倍数,0代表使用输入图片宽度",
93
+ },
94
+ ),
95
+ "height": (
96
+ "INT",
97
+ {
98
+ "default": 1024,
99
+ "min": 0,
100
+ "step": 16,
101
+ "tooltip": "必须为16的倍数,0代表使用输入图片高度",
102
+ },
103
+ ),
104
+ "safety_tolerance": (
105
+ "INT",
106
+ {"min": 0, "max": 5, "default": 2, "tooltip": "值越大越宽松"},
107
+ ),
108
+ "guidance": (
109
+ "FLOAT",
110
+ {"min": 1.5, "max": 10, "default": 4.5, "tooltip": "仅flex支持"},
111
+ ),
112
+ "steps": (
113
+ "INT",
114
+ {"min": 1, "max": 50, "default": 50, "tooltip": "仅flex支持"},
115
+ ),
116
+ },
117
+ "optional": {
118
+ "images": ("IMAGE",),
119
+ },
120
+ }
121
+
122
+ def handle_inputs(self, headers, prompt_id, **kwargs):
123
+ images = kwargs.get("images", None)
124
+ prompt = kwargs.get("prompt", "")
125
+ model = kwargs.get("model", "flux-2-pro")
126
+ width = kwargs.get("width", 1024)
127
+ height = kwargs.get("height", 1024)
128
+ safety_tolerance = kwargs.get("safety_tolerance", 2)
129
+ guidance = kwargs.get("guidance", 4.5)
130
+ steps = kwargs.get("steps", 50)
131
+ seed = kwargs.get("seed", 0)
132
+ if images is not None and len(images) > 8 and model == "flux-2-pro":
133
+ raise ValueError("Maximum number of images is 8 for flux-2-pro")
134
+ if images is not None and len(images) > 10 and model == "flux-2-flex":
135
+ raise ValueError("Maximum number of images is 10 for flux-2-flex")
136
+ if width < 0 or height < 0:
137
+ raise ValueError(
138
+ "Width and height must be greater than 0, or supply 0 value to use input image's original size"
139
+ )
140
+ if safety_tolerance < 0 or safety_tolerance > 6:
141
+ raise ValueError("Safety tolerance must be between 0 and 6")
142
+ if guidance < 1.5 or guidance > 10:
143
+ raise ValueError("Guidance must be between 1.5 and 10")
144
+ if steps < 1 or steps > 50:
145
+ raise ValueError("Steps must be between 1 and 50")
146
+ parts = []
147
+ for batch_number, img in enumerate(images if images is not None else []):
148
+ if img is not None:
149
+ url = self.upload_file(
150
+ tensor_to_bytesio(image=img, total_pixels=4096 * 4096),
151
+ f"{prompt_id}_{batch_number}.png",
152
+ headers,
153
+ )
154
+ parts.append(url)
155
+ data = {
156
+ "prompt": prompt,
157
+ "model": model,
158
+ "safety_tolerance": safety_tolerance,
159
+ "guidance": guidance,
160
+ "steps": steps,
161
+ "seed": seed,
162
+ }
163
+ if width > 0:
164
+ data["width"] = width
165
+ if height > 0:
166
+ data["height"] = height
167
+ if len(parts) > 0:
168
+ data["urls"] = parts
169
+ return data, "flux-2"
170
+
171
+ def handle_outputs(self, outputs):
172
+ images = self.combine_images(outputs[1])
173
+ return (images,)
@@ -0,0 +1,403 @@
1
+ from bizyairsdk import tensor_to_bytesio
2
+
3
+ from .trd_nodes_base import BizyAirTrdApiBaseNode
4
+
5
+
6
+ # FROM: https://github.com/ShmuelRonen/ComfyUI-NanoBanano/blob/9eeb8f2411fd0ff08791bdf5e24eec347456c8b8/nano_banano.py#L191
7
+ def build_prompt_for_operation(
8
+ prompt,
9
+ operation,
10
+ has_references=False,
11
+ aspect_ratio="auto",
12
+ character_consistency=True,
13
+ ):
14
+ """Build optimized prompt based on operation type"""
15
+
16
+ auto_aspect = (
17
+ "keep the original image aspect ratio"
18
+ if has_references
19
+ else "use an appropriate aspect ratio"
20
+ )
21
+ aspect_instructions = {
22
+ "1:1": "square format",
23
+ "16:9": "widescreen landscape format",
24
+ "9:16": "portrait format",
25
+ "4:3": "standard landscape format",
26
+ "3:4": "standard portrait format",
27
+ "auto": auto_aspect,
28
+ }
29
+
30
+ base_quality = "Generate a high-quality, photorealistic image"
31
+ format_instruction = f"in {aspect_instructions.get(aspect_ratio, auto_aspect)}"
32
+
33
+ if operation == "generate":
34
+ if has_references:
35
+ final_prompt = f"{base_quality} inspired by the style and elements of the reference images. {prompt}. {format_instruction}."
36
+ else:
37
+ final_prompt = f"{base_quality} of: {prompt}. {format_instruction}."
38
+
39
+ elif operation == "edit":
40
+ if not has_references:
41
+ return "Error: Edit operation requires reference images"
42
+ # No aspect ratio for edit - preserve original image dimensions
43
+ final_prompt = f"Edit the provided reference image(s). {prompt}. Maintain the original composition and quality while making the requested changes."
44
+
45
+ elif operation == "style_transfer":
46
+ if not has_references:
47
+ return "Error: Style transfer requires reference images"
48
+ final_prompt = f"Apply the style from the reference images to create: {prompt}. Blend the stylistic elements naturally. {format_instruction}."
49
+
50
+ elif operation == "object_insertion":
51
+ if not has_references:
52
+ return "Error: Object insertion requires reference images"
53
+ final_prompt = f"Insert or blend the following into the reference image(s): {prompt}. Ensure natural lighting, shadows, and perspective. {format_instruction}."
54
+
55
+ if character_consistency and has_references:
56
+ final_prompt += " Maintain character consistency and visual identity from the reference images."
57
+
58
+ return final_prompt
59
+
60
+
61
+ class NanoBanana(BizyAirTrdApiBaseNode):
62
+ NODE_DISPLAY_NAME = "NanoBanana"
63
+ RETURN_TYPES = ("IMAGE", "STRING")
64
+ CATEGORY = "☁️BizyAir/External APIs/Gemini"
65
+
66
+ @classmethod
67
+ def INPUT_TYPES(s):
68
+ return {
69
+ "required": {
70
+ "prompt": (
71
+ "STRING",
72
+ {
73
+ "multiline": True,
74
+ "default": "",
75
+ },
76
+ ),
77
+ "operation": (
78
+ ["generate", "edit", "style_transfer", "object_insertion"],
79
+ {
80
+ "default": "generate",
81
+ "tooltip": "Choose the type of image operation",
82
+ },
83
+ ),
84
+ "temperature": (
85
+ "FLOAT",
86
+ {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.05},
87
+ ),
88
+ "top_p": (
89
+ "FLOAT",
90
+ {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.05},
91
+ ),
92
+ "seed": ("INT", {"default": 0, "min": 0, "max": 2147483647}),
93
+ "max_tokens": ("INT", {"default": 8192, "min": 1, "max": 8192}),
94
+ },
95
+ "optional": {
96
+ "image": ("IMAGE",),
97
+ "image2": ("IMAGE",),
98
+ "image3": ("IMAGE",),
99
+ "image4": ("IMAGE",),
100
+ "image5": ("IMAGE",),
101
+ "quality": (
102
+ ["standard", "high"],
103
+ {"default": "high", "tooltip": "Image generation quality"},
104
+ ),
105
+ "aspect_ratio": (
106
+ ["1:1", "16:9", "9:16", "4:3", "3:4", "auto"],
107
+ {"default": "auto", "tooltip": "Output image aspect ratio"},
108
+ ),
109
+ "character_consistency": (
110
+ "BOOLEAN",
111
+ {
112
+ "default": True,
113
+ "tooltip": "Maintain character consistency across edits",
114
+ },
115
+ ),
116
+ },
117
+ }
118
+
119
+ def handle_inputs(self, headers, prompt_id, **kwargs):
120
+ operation = kwargs.get("operation", "generate")
121
+ temperature = kwargs.get("temperature", 1.0)
122
+ top_p = kwargs.get("top_p", 1.0)
123
+ seed = kwargs.get("seed", 0)
124
+ max_tokens = kwargs.get("max_tokens", 8192)
125
+ quality = kwargs.get("quality", "high")
126
+ aspect_ratio = kwargs.get("aspect_ratio", "auto")
127
+ character_consistency = kwargs.get("character_consistency", True)
128
+ prompt = kwargs.get("prompt", "")
129
+
130
+ parts = []
131
+ for i, img in enumerate(
132
+ [
133
+ kwargs.get("image", None),
134
+ kwargs.get("image2", None),
135
+ kwargs.get("image3", None),
136
+ kwargs.get("image4", None),
137
+ kwargs.get("image5", None),
138
+ ],
139
+ 1,
140
+ ):
141
+ if img is not None:
142
+ url = self.upload_file(
143
+ tensor_to_bytesio(image=img, total_pixels=4096 * 4096),
144
+ f"{prompt_id}_{i}.png",
145
+ headers,
146
+ )
147
+ parts.append(url)
148
+
149
+ prompt = build_prompt_for_operation(
150
+ prompt,
151
+ operation,
152
+ has_references=len(parts) > 0,
153
+ aspect_ratio=aspect_ratio,
154
+ character_consistency=character_consistency,
155
+ )
156
+ if quality == "high":
157
+ prompt += " Use the highest quality settings available."
158
+
159
+ data = {
160
+ "urls": parts,
161
+ "prompt": prompt,
162
+ "temperature": temperature,
163
+ "top_p": top_p,
164
+ "seed": seed,
165
+ "max_tokens": max_tokens,
166
+ }
167
+ if aspect_ratio != "auto":
168
+ data["aspect_ratio"] = aspect_ratio
169
+ return data, "gemini-2.5-flash-image"
170
+
171
+ def handle_outputs(self, outputs):
172
+ text = None
173
+ if len(outputs[1]) > 0:
174
+ image = outputs[1][0]
175
+ else:
176
+ raise ValueError("No image found in response")
177
+ if len(outputs[2]) > 0:
178
+ text = outputs[2][0]
179
+ return (image, text)
180
+
181
+
182
+ class NanoBananaPro(BizyAirTrdApiBaseNode):
183
+ NODE_DISPLAY_NAME = "NanoBananaPro"
184
+ RETURN_TYPES = ("IMAGE", "STRING")
185
+ RETURN_NAMES = ("image", "string")
186
+ CATEGORY = "☁️BizyAir/External APIs/Gemini"
187
+
188
+ @classmethod
189
+ def INPUT_TYPES(s):
190
+ return {
191
+ "required": {
192
+ "prompt": (
193
+ "STRING",
194
+ {
195
+ "multiline": True,
196
+ "default": "",
197
+ },
198
+ ),
199
+ "operation": (
200
+ ["generate", "edit", "style_transfer", "object_insertion"],
201
+ {
202
+ "default": "generate",
203
+ "tooltip": "Choose the type of image operation",
204
+ },
205
+ ),
206
+ "temperature": (
207
+ "FLOAT",
208
+ {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.05},
209
+ ),
210
+ "top_p": (
211
+ "FLOAT",
212
+ {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.05},
213
+ ),
214
+ "seed": ("INT", {"default": 0, "min": 0, "max": 2147483647}),
215
+ "max_tokens": ("INT", {"default": 32768, "min": 1, "max": 32768}),
216
+ "aspect_ratio": (
217
+ [
218
+ "1:1",
219
+ "2:3",
220
+ "3:2",
221
+ "3:4",
222
+ "4:3",
223
+ "4:5",
224
+ "5:4",
225
+ "9:16",
226
+ "16:9",
227
+ "21:9",
228
+ "auto",
229
+ ],
230
+ {"default": "auto", "tooltip": "Output image aspect ratio"},
231
+ ),
232
+ "resolution": (["1K", "2K", "4K", "auto"], {"default": "auto"}),
233
+ },
234
+ "optional": {
235
+ "images": ("IMAGE",),
236
+ "quality": (
237
+ ["standard", "high"],
238
+ {"default": "high", "tooltip": "Image generation quality"},
239
+ ),
240
+ "character_consistency": (
241
+ "BOOLEAN",
242
+ {
243
+ "default": True,
244
+ "tooltip": "Maintain character consistency across edits",
245
+ },
246
+ ),
247
+ },
248
+ }
249
+
250
+ def handle_inputs(self, headers, prompt_id, **kwargs):
251
+ operation = kwargs.get("operation", "generate")
252
+ temperature = kwargs.get("temperature", 1.0)
253
+ top_p = kwargs.get("top_p", 1.0)
254
+ seed = kwargs.get("seed", 0)
255
+ max_tokens = kwargs.get("max_tokens", 32768)
256
+ quality = kwargs.get("quality", "high")
257
+ aspect_ratio = kwargs.get("aspect_ratio", "auto")
258
+ resolution = kwargs.get("resolution", "auto")
259
+ images = kwargs.get("images", None)
260
+ character_consistency = kwargs.get("character_consistency", True)
261
+ prompt = kwargs.get("prompt", "")
262
+
263
+ if images is not None and len(images) > 14:
264
+ raise ValueError("Maximum number of images is 14")
265
+ parts = []
266
+ for batch_number, img in enumerate(images if images is not None else []):
267
+ if img is not None:
268
+ url = self.upload_file(
269
+ tensor_to_bytesio(image=img, total_pixels=4096 * 4096),
270
+ f"{prompt_id}_{batch_number}.png",
271
+ headers,
272
+ )
273
+ parts.append(url)
274
+
275
+ prompt = build_prompt_for_operation(
276
+ prompt,
277
+ operation,
278
+ has_references=len(parts) > 0,
279
+ aspect_ratio=aspect_ratio,
280
+ character_consistency=character_consistency,
281
+ )
282
+ if quality == "high":
283
+ prompt += " Use the highest quality settings available."
284
+
285
+ data = {
286
+ "urls": parts,
287
+ "prompt": prompt,
288
+ "temperature": temperature,
289
+ "top_p": top_p,
290
+ "seed": seed,
291
+ "max_tokens": max_tokens,
292
+ }
293
+ if aspect_ratio != "auto":
294
+ data["aspect_ratio"] = aspect_ratio
295
+ if resolution != "auto":
296
+ data["resolution"] = resolution
297
+ return data, "gemini-3-pro-image-preview"
298
+
299
+ def handle_outputs(self, outputs):
300
+ text = None
301
+ if len(outputs[1]) > 0:
302
+ image = outputs[1][0]
303
+ else:
304
+ raise ValueError("No image found in response")
305
+ if len(outputs[2]) > 0:
306
+ text = outputs[2][0]
307
+ return (image, text)
308
+
309
+
310
+ class NanoBananaProOfficial(BizyAirTrdApiBaseNode):
311
+ @classmethod
312
+ def INPUT_TYPES(s):
313
+ return {
314
+ "required": {
315
+ "prompt": (
316
+ "STRING",
317
+ {
318
+ "multiline": True,
319
+ "default": "",
320
+ },
321
+ ),
322
+ "temperature": (
323
+ "FLOAT",
324
+ {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.05},
325
+ ),
326
+ "top_p": (
327
+ "FLOAT",
328
+ {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.05},
329
+ ),
330
+ "seed": ("INT", {"default": 0, "min": 0, "max": 2147483647}),
331
+ "max_tokens": ("INT", {"default": 32768, "min": 1, "max": 32768}),
332
+ "aspect_ratio": (
333
+ [
334
+ "1:1",
335
+ "2:3",
336
+ "3:2",
337
+ "3:4",
338
+ "4:3",
339
+ "4:5",
340
+ "5:4",
341
+ "9:16",
342
+ "16:9",
343
+ "21:9",
344
+ "auto",
345
+ ],
346
+ {"default": "auto", "tooltip": "Output image aspect ratio"},
347
+ ),
348
+ "resolution": (["1K", "2K", "4K", "auto"], {"default": "auto"}),
349
+ },
350
+ "optional": {
351
+ "images": ("IMAGE",),
352
+ },
353
+ }
354
+
355
+ RETURN_TYPES = ("IMAGE", "STRING")
356
+ RETURN_NAMES = ("image", "string")
357
+ CATEGORY = "☁️BizyAir/External APIs/Gemini"
358
+ NODE_DISPLAY_NAME = "NanoBananaPro (Official Parameters)"
359
+
360
+ def handle_inputs(self, headers, prompt_id, **kwargs):
361
+ prompt = kwargs.get("prompt", "")
362
+ temperature = kwargs.get("temperature", 1.0)
363
+ top_p = kwargs.get("top_p", 1.0)
364
+ seed = kwargs.get("seed", 0)
365
+ max_tokens = kwargs.get("max_tokens", 32768)
366
+ aspect_ratio = kwargs.get("aspect_ratio", "auto")
367
+ resolution = kwargs.get("resolution", "auto")
368
+ images = kwargs.get("images", None)
369
+ if images is not None and len(images) > 14:
370
+ raise ValueError("Maximum number of images is 14")
371
+ parts = []
372
+ for batch_number, img in enumerate(images if images is not None else []):
373
+ if img is not None:
374
+ url = self.upload_file(
375
+ tensor_to_bytesio(image=img, total_pixels=4096 * 4096),
376
+ f"{prompt_id}_{batch_number}.png",
377
+ headers,
378
+ )
379
+ parts.append(url)
380
+
381
+ data = {
382
+ "urls": parts,
383
+ "prompt": prompt,
384
+ "temperature": temperature,
385
+ "top_p": top_p,
386
+ "seed": seed,
387
+ "max_tokens": max_tokens,
388
+ }
389
+ if aspect_ratio != "auto":
390
+ data["aspect_ratio"] = aspect_ratio
391
+ if resolution != "auto":
392
+ data["resolution"] = resolution
393
+ return data, "gemini-3-pro-image-preview"
394
+
395
+ def handle_outputs(self, outputs):
396
+ text = None
397
+ if len(outputs[1]) > 0:
398
+ image = outputs[1][0]
399
+ else:
400
+ raise ValueError("No image found in response")
401
+ if len(outputs[2]) > 0:
402
+ text = outputs[2][0]
403
+ return (image, text)
@@ -0,0 +1,101 @@
1
+ from bizyairsdk import tensor_to_bytesio
2
+
3
+ from .trd_nodes_base import BizyAirTrdApiBaseNode
4
+
5
+
6
+ class GPT_IMAGE_1_T2I_API(BizyAirTrdApiBaseNode):
7
+ NODE_DISPLAY_NAME = "GPT Image 1 Text To Image"
8
+ RETURN_TYPES = ("IMAGE",)
9
+ RETURN_NAMES = ("images",)
10
+ CATEGORY = "☁️BizyAir/External APIs/OpenAI"
11
+
12
+ @classmethod
13
+ def INPUT_TYPES(cls):
14
+ return {
15
+ "required": {
16
+ "prompt": (
17
+ "STRING",
18
+ {
19
+ "multiline": True,
20
+ "default": "",
21
+ },
22
+ ),
23
+ "size": (["1:1", "2:3", "3:2"], {"default": "1:1"}),
24
+ "variants": ([1, 2, 4], {"default": 1}),
25
+ },
26
+ }
27
+
28
+ def handle_inputs(self, headers, prompt_id, **kwargs):
29
+ prompt = kwargs.get("prompt", "")
30
+ size = kwargs.get("size", "1:1")
31
+ variants = kwargs.get("variants", 1)
32
+ data = {
33
+ "prompt": prompt,
34
+ "size": size,
35
+ "variants": variants,
36
+ }
37
+ if variants == 4:
38
+ data["provider"] = "KieAI"
39
+ return data, "gpt-image-1"
40
+
41
+ def handle_outputs(self, outputs):
42
+ images = self.combine_images(outputs[1])
43
+ return (images,)
44
+
45
+
46
+ class GPT_IMAGE_EDIT_API(BizyAirTrdApiBaseNode):
47
+ NODE_DISPLAY_NAME = "GPT Image Edit"
48
+ RETURN_TYPES = ("IMAGE",)
49
+ RETURN_NAMES = ("images",)
50
+ CATEGORY = "☁️BizyAir/External APIs/OpenAI"
51
+
52
+ @classmethod
53
+ def INPUT_TYPES(cls):
54
+ return {
55
+ "required": {
56
+ "images": ("IMAGE",),
57
+ "prompt": (
58
+ "STRING",
59
+ {
60
+ "multiline": True,
61
+ "default": "",
62
+ },
63
+ ),
64
+ "size": (["1:1", "2:3", "3:2"], {"default": "1:1"}),
65
+ "variants": ([1, 2, 4], {"default": 1}),
66
+ },
67
+ }
68
+
69
+ def handle_inputs(self, headers, prompt_id, **kwargs):
70
+ images = kwargs.get("images", None)
71
+ if images is None:
72
+ raise ValueError("Images are required")
73
+ prompt = kwargs.get("prompt", "")
74
+ size = kwargs.get("size", "1:1")
75
+ variants = kwargs.get("variants", 1)
76
+
77
+ urls = []
78
+ for batch_number, img in enumerate(images if images is not None else []):
79
+ if img is not None:
80
+ url = self.upload_file(
81
+ tensor_to_bytesio(image=img, total_pixels=4096 * 4096),
82
+ f"{prompt_id}_{batch_number}.png",
83
+ headers,
84
+ )
85
+ urls.append(url)
86
+ if len(urls) == 0:
87
+ raise ValueError("At least one image is required")
88
+
89
+ data = {
90
+ "urls": urls,
91
+ "prompt": prompt,
92
+ "size": size,
93
+ "variants": variants,
94
+ }
95
+ if variants == 4:
96
+ data["provider"] = "KieAI"
97
+ return data, "gpt-image-1"
98
+
99
+ def handle_outputs(self, outputs):
100
+ images = self.combine_images(outputs[1])
101
+ return (images,)