feat: 新增「返回格式」参数支持 URL 模式图片返回
- 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
This commit is contained in:
+68
-30
@@ -55,13 +55,14 @@ 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 端点路径
|
||||
@@ -71,65 +72,70 @@ class GeminiAPIClient(BaseAPIClient):
|
||||
# 特殊处理:动态端点模型(根据分辨率选择)
|
||||
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 = "/v1beta/models/gemini-3-pro-image-preview-2k-url:generateContent"
|
||||
|
||||
# 其他模型:从配置文件读取端点
|
||||
endpoint = get_model_endpoint(model)
|
||||
if endpoint:
|
||||
return endpoint
|
||||
else:
|
||||
# 其他模型:从配置文件读取端点
|
||||
endpoint = get_model_endpoint(model)
|
||||
if not endpoint:
|
||||
# 兜底:使用标准模式端点
|
||||
endpoint = "/v1beta/models/gemini-3-pro-image-preview:generateContent"
|
||||
|
||||
# 兜底:使用标准模式端点
|
||||
return "/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 时返回中文错误文案。"""
|
||||
@@ -422,6 +428,33 @@ class GeminiAPIClient(BaseAPIClient):
|
||||
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:
|
||||
text = part["text"]
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user