From 82995946465ee1b6d8b14b4da300f8318b8ee778 Mon Sep 17 00:00:00 2001 From: Jony <951565127@qq.com> Date: Sun, 26 Apr 2026 18:00:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E3=80=8C=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=E6=A0=BC=E5=BC=8F=E3=80=8D=E5=8F=82=E6=95=B0=E6=94=AF?= =?UTF-8?q?=E6=8C=81=20URL=20=E6=A8=A1=E5=BC=8F=E5=9B=BE=E7=89=87=E8=BF=94?= =?UTF-8?q?=E5=9B=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - nano_banana_pro / batch_nano_banana_pro 新增「返回格式」下拉参数(url/base64,默认 url) - url 模式下请求端点自动追加 ?image_format=url 查询参数 - parse_response_async 新增 fileData 分支,解析服务端 URL 返回并异步下载图片 - batch 节点「代理加速」参数重命名为「代理端口(如7897)」,与 pro 节点保持一致 - image_format 完整透传:UI → generate → _process_batch_async → _generate_single_task → generate_single_async → get_endpoint --- clients/gemini_client.py | 112 ++++++++++++++++++++++----------- nodes/batch_nano_banana_pro.py | 13 +++- nodes/nano_banana_pro.py | 11 ++++ 3 files changed, 97 insertions(+), 39 deletions(-) diff --git a/clients/gemini_client.py b/clients/gemini_client.py index 570095f..d703d38 100644 --- a/clients/gemini_client.py +++ b/clients/gemini_client.py @@ -55,81 +55,87 @@ class GeminiAPIClient(BaseAPIClient): return None return f"http://127.0.0.1:{port}" - def get_endpoint(self, model: str = "", resolution: str = "2K", **kwargs) -> str: + def get_endpoint(self, model: str = "", resolution: str = "2K", image_format: str = "base64", **kwargs) -> str: """ 根据模型和分辨率获取 API 端点 - + Args: model: 模型名称 resolution: 分辨率(1K, 2K, 4K) - + image_format: 返回格式,"url" 时追加 ?image_format=url 查询参数 + Returns: API 端点路径 """ from ..models_config import get_model_endpoint - + # 特殊处理:动态端点模型(根据分辨率选择) if model == "nano-banana-pro-限时特价": if resolution == "1K": - return "/v1beta/models/nano-banana-pro:generateContent" + endpoint = "/v1beta/models/nano-banana-pro:generateContent" elif resolution == "2K": - return "/v1beta/models/nano-banana-pro-2k:generateContent" + endpoint = "/v1beta/models/nano-banana-pro-2k:generateContent" elif resolution == "4K": - return "/v1beta/models/nano-banana-pro-4k:generateContent" + endpoint = "/v1beta/models/nano-banana-pro-4k:generateContent" else: - return "/v1beta/models/nano-banana-pro-2k:generateContent" + endpoint = "/v1beta/models/nano-banana-pro-2k:generateContent" elif model == "nano-banana-2-限时特价": if resolution == "512px": - return "/v1beta/models/nano-banana-2-0.5k:generateContent" + endpoint = "/v1beta/models/nano-banana-2-0.5k:generateContent" elif resolution == "1K": - return "/v1beta/models/nano-banana-2-1k:generateContent" + endpoint = "/v1beta/models/nano-banana-2-1k:generateContent" elif resolution == "2K": - return "/v1beta/models/nano-banana-2-2k:generateContent" + endpoint = "/v1beta/models/nano-banana-2-2k:generateContent" elif resolution == "4K": - return "/v1beta/models/nano-banana-2-4k:generateContent" + endpoint = "/v1beta/models/nano-banana-2-4k:generateContent" else: - return "/v1beta/models/nano-banana-2-2k:generateContent" + endpoint = "/v1beta/models/nano-banana-2-2k:generateContent" elif model == "nano-banana-2-官方计费": if resolution == "512px": - return "/v1beta/models/nano-banana-2-0.5k-official:generateContent" + endpoint = "/v1beta/models/nano-banana-2-0.5k-official:generateContent" elif resolution == "1K": - return "/v1beta/models/nano-banana-2-1k-official:generateContent" + endpoint = "/v1beta/models/nano-banana-2-1k-official:generateContent" elif resolution == "2K": - return "/v1beta/models/nano-banana-2-2k-official:generateContent" + endpoint = "/v1beta/models/nano-banana-2-2k-official:generateContent" elif resolution == "4K": - return "/v1beta/models/nano-banana-2-4k-official:generateContent" + endpoint = "/v1beta/models/nano-banana-2-4k-official:generateContent" else: - return "/v1beta/models/nano-banana-2-2k-official:generateContent" + endpoint = "/v1beta/models/nano-banana-2-2k-official:generateContent" elif model == "nano-banana-pro-官方计费": if resolution == "1K": - return "/v1beta/models/nano-banana-pro-1k-official:generateContent" + endpoint = "/v1beta/models/nano-banana-pro-1k-official:generateContent" elif resolution == "2K": - return "/v1beta/models/nano-banana-pro-2k-official:generateContent" + endpoint = "/v1beta/models/nano-banana-pro-2k-official:generateContent" elif resolution == "4K": - return "/v1beta/models/nano-banana-pro-4k-official:generateContent" + endpoint = "/v1beta/models/nano-banana-pro-4k-official:generateContent" else: - return "/v1beta/models/nano-banana-pro-2k-official:generateContent" - + endpoint = "/v1beta/models/nano-banana-pro-2k-official:generateContent" + elif model == "gemini-3-pro-image-preview-url": if resolution == "1K": - return "/v1beta/models/gemini-3-pro-image-preview-url:generateContent" + endpoint = "/v1beta/models/gemini-3-pro-image-preview-url:generateContent" elif resolution == "2K": - return "/v1beta/models/gemini-3-pro-image-preview-2k-url:generateContent" + endpoint = "/v1beta/models/gemini-3-pro-image-preview-2k-url:generateContent" elif resolution == "4K": - return "/v1beta/models/gemini-3-pro-image-preview-4k-url:generateContent" + endpoint = "/v1beta/models/gemini-3-pro-image-preview-4k-url:generateContent" else: - return "/v1beta/models/gemini-3-pro-image-preview-2k-url:generateContent" - - # 其他模型:从配置文件读取端点 - endpoint = get_model_endpoint(model) - if endpoint: - return endpoint - - # 兜底:使用标准模式端点 - return "/v1beta/models/gemini-3-pro-image-preview:generateContent" + endpoint = "/v1beta/models/gemini-3-pro-image-preview-2k-url:generateContent" + + else: + # 其他模型:从配置文件读取端点 + endpoint = get_model_endpoint(model) + if not endpoint: + # 兜底:使用标准模式端点 + endpoint = "/v1beta/models/gemini-3-pro-image-preview:generateContent" + + # url 模式:追加查询参数 + if image_format == "url": + endpoint = endpoint + "?image_format=url" + + return endpoint def get_http_error_message(self, status_code: int, error_message: str) -> Optional[str]: """Gemini 请求 429/503/504 时返回中文错误文案。""" @@ -415,12 +421,39 @@ class GeminiAPIClient(BaseAPIClient): if img_data: img = decode_base64_to_pil(img_data) images.append(img) - + # 记录格式信息 if format_info["type"] is None: format_info["type"] = "base64" format_info["size"] = len(img_data) * 3 / 4 # Base64 解码后的字节数 format_info["resolution"] = f"{img.size[0]}x{img.size[1]}" + + # 方式1b: fileData(?image_format=url 模式下服务端返回 URL 替代 inlineData) + elif "fileData" in part: + if part.get("thought") is True: + continue + file_data = part["fileData"] + url = file_data.get("fileUri") or file_data.get("file_uri", "") + if url: + try: + download_start = time.time() + async with session.get(url) as img_response: + if img_response.status == 200: + img_bytes = await img_response.read() + download_time = time.time() - download_start + img_size = len(img_bytes) + speed = img_size / download_time if download_time > 0 else 0 + + img = Image.open(BytesIO(img_bytes)) + images.append(img) + + if format_info["type"] is None: + format_info["type"] = "url" + format_info["size"] = img_size + format_info["resolution"] = f"{img.size[0]}x{img.size[1]}" + format_info["download_speed"] = speed + except Exception: + pass # 静默失败 # 方式2: text 中的 URL - 改为异步下载 elif "text" in part: @@ -525,6 +558,7 @@ class GeminiAPIClient(BaseAPIClient): debug_request: bool = False, enable_grounding: bool = False, enable_image_search: bool = False, + image_format: str = "base64", ) -> tuple[List[Image.Image], Dict[str, Any]]: """ 单次异步生成请求(极简单行日志) @@ -555,7 +589,7 @@ class GeminiAPIClient(BaseAPIClient): # ========== 1. 构建请求 ========== build_start = time.time() - endpoint = self.get_endpoint(model=model, resolution=resolution) + endpoint = self.get_endpoint(model=model, resolution=resolution, image_format=image_format) request_body = self.build_request_body( prompt=prompt, images=images, @@ -697,6 +731,7 @@ class GeminiAPIClient(BaseAPIClient): debug_request: bool = False, enable_grounding: bool = False, enable_image_search: bool = False, + image_format: str = "base64", ) -> List[Image.Image]: """ 批量全并发生成 - 改进版:支持分批处理和内存管理 @@ -760,6 +795,7 @@ class GeminiAPIClient(BaseAPIClient): debug_request=debug_request, enable_grounding=enable_grounding, enable_image_search=enable_image_search, + image_format=image_format, ), name=f"task_{task_index}" ) @@ -831,6 +867,7 @@ class GeminiAPIClient(BaseAPIClient): debug_request: bool = False, enable_grounding: bool = False, enable_image_search: bool = False, + image_format: str = "base64", ) -> List[Image.Image]: """ 同步生成接口(用于 ComfyUI) @@ -863,6 +900,7 @@ class GeminiAPIClient(BaseAPIClient): debug_request=debug_request, enable_grounding=enable_grounding, enable_image_search=enable_image_search, + image_format=image_format, ) return self.run_async_in_thread(coro) diff --git a/nodes/batch_nano_banana_pro.py b/nodes/batch_nano_banana_pro.py index 141d25b..daccda1 100644 --- a/nodes/batch_nano_banana_pro.py +++ b/nodes/batch_nano_banana_pro.py @@ -215,7 +215,7 @@ class BatchNanoBananaPro: "default": "不配对" }) - optional_inputs["代理加速"] = ("STRING", { + optional_inputs["代理端口(如7897)"] = ("STRING", { "default": "", "multiline": False, "placeholder": "本地代理端口,如 7897(Clash Verge)或 10808(v2rayN),留空不使用" @@ -242,6 +242,9 @@ class BatchNanoBananaPro: "图片搜索(联网)": (["关闭", "打开"], { "default": "关闭" }), + "返回格式": (["url", "base64"], { + "default": "url" + }), "seed": ("INT", { "default": 0, "min": 0, @@ -412,6 +415,7 @@ class BatchNanoBananaPro: enable_grounding: bool = True, enable_image_search: bool = False, base_filename: str = None, + image_format: str = "url", ) -> dict: """ 执行单个生成任务 @@ -458,6 +462,7 @@ class BatchNanoBananaPro: debug_request=REQUEST_LOG_ENABLED, enable_grounding=enable_grounding, enable_image_search=enable_image_search, + image_format=image_format, ) if gen_result: # 正确解包元组:第一个元素是图像列表,第二个是计时信息 @@ -532,6 +537,7 @@ class BatchNanoBananaPro: prompts_per_task: Optional[List[str]] = None, enable_grounding: bool = True, enable_image_search: bool = False, + image_format: str = "url", ) -> List[dict]: """ 异步批量处理所有任务 - 改进版:支持分批保存 @@ -623,6 +629,7 @@ class BatchNanoBananaPro: enable_grounding=enable_grounding, enable_image_search=enable_image_search, base_filename=base_filename, + image_format=image_format, ) ) tasks.append(task) @@ -748,7 +755,8 @@ class BatchNanoBananaPro: # 从 kwargs 提取搜索参数(界面显示为「关闭/打开」,转为 bool 供调用) enable_grounding: bool = (kwargs.pop("谷歌搜索(联网)", "关闭") == "打开") enable_image_search: bool = (kwargs.pop("图片搜索(联网)", "关闭") == "打开") - proxy_port: str = kwargs.pop("代理加速", "") + proxy_port: str = kwargs.pop("代理端口(如7897)", "") + image_format: str = kwargs.pop("返回格式", "url") try: @@ -916,6 +924,7 @@ class BatchNanoBananaPro: prompts_per_task=prompts_per_task, enable_grounding=enable_grounding, enable_image_search=enable_image_search, + image_format=image_format, ) ) except Exception as e: diff --git a/nodes/nano_banana_pro.py b/nodes/nano_banana_pro.py index c45c122..9daad2b 100644 --- a/nodes/nano_banana_pro.py +++ b/nodes/nano_banana_pro.py @@ -183,6 +183,9 @@ class NanoBananaPro: "图片搜索(联网)": (["关闭", "打开"], { "default": "关闭" }), + "返回格式": (["url", "base64"], { + "default": "url" + }), "seed": ("INT", { "default": 0, "min": 0, @@ -293,6 +296,7 @@ class NanoBananaPro: enable_grounding: bool = False, enable_image_search: bool = False, save_to_disk: bool = True, + image_format: str = "url", ) -> dict: """执行单个生成任务""" result = { @@ -317,6 +321,7 @@ class NanoBananaPro: debug_request=REQUEST_LOG_ENABLED, enable_grounding=enable_grounding, enable_image_search=enable_image_search, + image_format=image_format, ) if gen_result: images_list, _ = gen_result @@ -352,6 +357,7 @@ class NanoBananaPro: enable_grounding: bool = False, enable_image_search: bool = False, save_to_disk: bool = True, + image_format: str = "url", ) -> List[dict]: """异步批量处理:每个提示词独立调用 API""" # 构建任务列表:(prompt, sub_index) 用于 images_per_prompt > 1 的情况 @@ -394,6 +400,7 @@ class NanoBananaPro: enable_grounding=enable_grounding, enable_image_search=enable_image_search, save_to_disk=save_to_disk, + image_format=image_format, ) ) tasks.append(task) @@ -471,6 +478,7 @@ class NanoBananaPro: enable_grounding: bool = (kwargs.pop("谷歌搜索(联网)", "关闭") == "打开") enable_image_search: bool = (kwargs.pop("图片搜索(联网)", "关闭") == "打开") proxy_port: str = kwargs.pop("代理端口(如7897)", "") + image_format: str = kwargs.pop("返回格式", "url") # 创建 ComfyUI 原生进度条 pbar = None @@ -629,6 +637,7 @@ class NanoBananaPro: enable_grounding=enable_grounding, enable_image_search=enable_image_search, save_to_disk=False, + image_format=image_format, ) ) finally: @@ -694,6 +703,7 @@ class NanoBananaPro: debug_request=REQUEST_LOG_ENABLED, enable_grounding=enable_grounding, enable_image_search=enable_image_search, + image_format=image_format, ) break except RuntimeError as e: @@ -731,6 +741,7 @@ class NanoBananaPro: enable_grounding=enable_grounding, enable_image_search=enable_image_search, save_to_disk=False, + image_format=image_format, ) ) finally: