feat: GPT Image 节点新增质量参数、批量提示词及模型调整

- 新增「质量」下拉参数(高/中/低/自动),默认 auto,两个接口均传递 quality 字段
- 支持批量提示词模式:prompt 中用单独一行 --- 分隔多条提示词,逐条调用 API 并合并输出
- 模型列表更新:移除 gpt-image-1 / gpt-image-1-特价,新增 gpt-image-2,默认改为 gpt-image-2
- size 字段始终传递(auto 也上传),移除 seed 参数传递

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
This commit is contained in:
o1key
2026-04-22 14:59:43 +08:00
co-authored by Claude Sonnet 4.5
parent afa732b93a
commit 949f7bb180
2 changed files with 87 additions and 47 deletions
+7 -14
View File
@@ -33,7 +33,6 @@ _ENDPOINT_EDITS = "/v1/images/edits/"
# ── 模型名映射(UI 显示名 → API 实际参数名)───────────────────────────────── # ── 模型名映射(UI 显示名 → API 实际参数名)─────────────────────────────────
_MODEL_NAME_MAP = { _MODEL_NAME_MAP = {
"gpt-image-1-特价": "gpt-image-1-special",
"gpt-image-1.5-特价": "gpt-image-1.5-special", "gpt-image-1.5-特价": "gpt-image-1.5-special",
"gpt-image-2-特价": "gpt-image-2-special", "gpt-image-2-特价": "gpt-image-2-special",
} }
@@ -215,13 +214,7 @@ class GptImageClient:
"moderation": "low", "moderation": "low",
} }
# size = "auto" 时不传该字段,让 API 自行决定 body["size"] = size if size else "auto"
if size and size != "auto":
body["size"] = size
# seed > 0 时才传递(0 视为不指定)
if seed > 0:
body["seed"] = seed
# 图生图:将 tensor 转成 data URI 内联 # 图生图:将 tensor 转成 data URI 内联
if image_tensor is not None: if image_tensor is not None:
@@ -298,15 +291,15 @@ class GptImageClient:
image_tensor = image_tensor.unsqueeze(0) # [H,W,C] → [1,H,W,C] image_tensor = image_tensor.unsqueeze(0) # [H,W,C] → [1,H,W,C]
num_images = image_tensor.shape[0] num_images = image_tensor.shape[0]
# o1key 中转服务的 edits 接口暂不支持 quality / background / moderation / seed # o1key 中转服务的 edits 接口暂不支持 background / moderation / seed
# 待服务方更新后可重新加入。 # 待服务方更新后可重新加入。
form = aiohttp.FormData() form = aiohttp.FormData()
form.add_field("model", api_model) form.add_field("model", api_model)
form.add_field("prompt", prompt) form.add_field("prompt", prompt)
form.add_field("n", str(n)) form.add_field("n", str(n))
form.add_field("quality", quality)
if size and size != "auto": form.add_field("size", size if size else "auto")
form.add_field("size", size)
# 多图:用 image[] 数组字段逐张附加,支持 gpt-image-1.5 最多 16 张 # 多图:用 image[] 数组字段逐张附加,支持 gpt-image-1.5 最多 16 张
for i in range(num_images): for i in range(num_images):
+80 -33
View File
@@ -7,6 +7,7 @@ import time
import torch import torch
from ..clients.gpt_image_client import GptImageClient from ..clients.gpt_image_client import GptImageClient
from ..utils.image_utils import parse_batch_prompts
class O1keyGPTImage: class O1keyGPTImage:
@@ -17,13 +18,15 @@ class O1keyGPTImage:
- 文生图:仅提供 prompt - 文生图:仅提供 prompt
- 图生图:提供 prompt + 图片(无遮罩) - 图生图:提供 prompt + 图片(无遮罩)
- 图像编辑:提供 prompt + 图片 + 遮罩(白色区域将被替换) - 图像编辑:提供 prompt + 图片 + 遮罩(白色区域将被替换)
- 批量模式:prompt 中用单独一行 --- 分隔多条提示词
参数: 参数:
- prompt : 文本提示词(多行) - prompt : 文本提示词(多行;用 --- 独占一行分隔批量提示词
- 模型 : 模型选择 - 模型 : 模型选择
- 分辨率 : 图像尺寸(auto 让 API 自动决定) - 分辨率 : 图像尺寸(auto 让 API 自动决定)
- 生图数量 : 生成数量 1-8 - 生图数量 : 每条提示词生成数量 1-8
- seed : 随机种子(0 表示不指定) - seed : 随机种子(0 表示不指定)
- 质量 : 生成质量
- 图片 : 可选参考图(用于图生图或编辑) - 图片 : 可选参考图(用于图生图或编辑)
- 遮罩 : 可选蒙版(白色区域将被替换) - 遮罩 : 可选蒙版(白色区域将被替换)
""" """
@@ -35,18 +38,17 @@ class O1keyGPTImage:
"prompt": ("STRING", { "prompt": ("STRING", {
"default": "", "default": "",
"multiline": True, "multiline": True,
"tooltip": "Text prompt for GPT Image", "tooltip": "Text prompt for GPT Image. Use --- on its own line to separate batch prompts.",
}), }),
}, },
"optional": { "optional": {
"模型": ([ "模型": ([
"gpt-image-1", "gpt-image-2",
"gpt-image-1.5", "gpt-image-1.5",
"gpt-image-2-特价", "gpt-image-2-特价",
"gpt-image-1-特价",
"gpt-image-1.5-特价", "gpt-image-1.5-特价",
], { ], {
"default": "gpt-image-1.5", "default": "gpt-image-2",
}), }),
"分辨率": ([ "分辨率": ([
"auto(默认)", "auto(默认)",
@@ -67,7 +69,7 @@ class O1keyGPTImage:
"max": 8, "max": 8,
"step": 1, "step": 1,
"display": "number", "display": "number",
"tooltip": "How many images to generate", "tooltip": "How many images to generate per prompt",
}), }),
"seed": ("INT", { "seed": ("INT", {
"default": 0, "default": 0,
@@ -78,6 +80,10 @@ class O1keyGPTImage:
"control_after_generate": True, "control_after_generate": True,
"tooltip": "Random seed (0 = not specified)", "tooltip": "Random seed (0 = not specified)",
}), }),
"质量": (["", "", "", "自动"], {
"default": "自动",
"tooltip": "Image quality: 高=high, 中=medium, 低=low, 自动=auto",
}),
"图片": ("IMAGE", { "图片": ("IMAGE", {
"tooltip": "Optional reference image for image editing.", "tooltip": "Optional reference image for image editing.",
}), }),
@@ -96,34 +102,37 @@ class O1keyGPTImage:
def generate( def generate(
self, self,
prompt: str, prompt: str,
模型: str = "gpt-image-1.5", 模型: str = "gpt-image-2",
分辨率: str = "auto", 分辨率: str = "auto",
质量: str = "自动",
生图数量: int = 1, 生图数量: int = 1,
seed: int = 0, seed: int = 0,
图片=None, 图片=None,
遮罩=None, 遮罩=None,
): ):
""" """
生成图像(文生图 / 图生图 / 图像编辑) 生成图像(文生图 / 图生图 / 图像编辑 / 批量提示词
路由逻辑: 路由逻辑:
- 无图片 → generations 接口(文生图) - 无图片 → generations 接口(文生图)
- 有图片,无遮罩 → edits 接口(图生图) - 有图片,无遮罩 → edits 接口(图生图)
- 有图片,有遮罩 → edits 接口(图像编辑 + 蒙版) - 有图片,有遮罩 → edits 接口(图像编辑 + 蒙版)
- prompt 含 --- → 批量模式,逐条调用上述接口
""" """
start_time = time.time() start_time = time.time()
# ── 1. 参数校验 ─────────────────────────────────────────────────────── # ── 1. 参数校验 ───────────────────────────────────────────────────────
if not prompt or not prompt.strip():
raise ValueError("提示词不能为空")
if 遮罩 is not None and 图片 is None: if 遮罩 is not None and 图片 is None:
raise ValueError("提供了遮罩但未提供图片,请同时提供图片和遮罩") raise ValueError("提供了遮罩但未提供图片,请同时提供图片和遮罩")
# ── 2. 解析分辨率显示值 → API 参数值 ────────────────────────────────── # ── 2. 解析分辨率显示值 → API 参数值 ──────────────────────────────────
size = 分辨率.split("")[0].strip() size = 分辨率.split("")[0].strip()
# ── 2. 创建客户端 ───────────────────────────────────────────────────── # ── 2b. 解析质量显示值 → API 参数值 ───────────────────────────────────
_quality_map = {"": "high", "": "medium", "": "low", "自动": "auto"}
quality = _quality_map.get(质量, "auto")
# ── 3. 创建客户端 ─────────────────────────────────────────────────────
try: try:
client = GptImageClient() client = GptImageClient()
except ValueError as e: except ValueError as e:
@@ -132,28 +141,66 @@ class O1keyGPTImage:
raise ValueError("未授权!") from None raise ValueError("未授权!") from None
raise raise
# ── 3. 调用 API ─────────────────────────────────────────────────────── # ── 4. 解析批量提示词 ─────────────────────────────────────────────────
try: batch_prompts = parse_batch_prompts(prompt)
pil_images = client.run_sync(
prompt=prompt,
model=模型,
quality="low",
background="auto",
size=size,
n=生图数量,
seed=seed,
image_tensor=图片,
mask_tensor=遮罩,
)
except Exception as e:
error_msg = str(e).split('\n')[0]
print(f"[o1key GPT Image] ❌ {error_msg}")
raise RuntimeError(error_msg) from None
# ── 4. PIL → tensor ─────────────────────────────────────────────────── # ── 5. 调用 API ───────────────────────────────────────────────────────
output_tensor = GptImageClient._pil_list_to_tensor(pil_images) all_pil_images = []
# ── 5. 完成日志 ─────────────────────────────────────────────────────── if batch_prompts:
# 批量模式:逐条提示词调用
total = len(batch_prompts)
print(f"[o1key GPT Image] 批量模式 | {total} 条提示词 | 每条生成 {生图数量}")
for idx, p in enumerate(batch_prompts, 1):
try:
pil_images = client.run_sync(
prompt=p,
model=模型,
quality=quality,
background="auto",
size=size,
n=生图数量,
seed=seed,
image_tensor=图片,
mask_tensor=遮罩,
)
all_pil_images.extend(pil_images)
snippet = p[:30] + ("..." if len(p) >= 30 else "")
print(f"[o1key GPT Image] [{idx}/{total}] ✓ {snippet}")
except Exception as e:
error_msg = str(e).split('\n')[0]
snippet = p[:30] + ("..." if len(p) >= 30 else "")
print(f"[o1key GPT Image] [{idx}/{total}] ❌ {snippet}{error_msg}")
else:
# 单提示词模式
if not prompt or not prompt.strip():
raise ValueError("提示词不能为空")
try:
pil_images = client.run_sync(
prompt=prompt,
model=模型,
quality=quality,
background="auto",
size=size,
n=生图数量,
seed=seed,
image_tensor=图片,
mask_tensor=遮罩,
)
all_pil_images.extend(pil_images)
except Exception as e:
error_msg = str(e).split('\n')[0]
print(f"[o1key GPT Image] ❌ {error_msg}")
raise RuntimeError(error_msg) from None
# ── 6. 检查是否有可用图像 ─────────────────────────────────────────────
if not all_pil_images:
raise RuntimeError("所有提示词均生成失败,无可用图像输出")
# ── 7. PIL → tensor ───────────────────────────────────────────────────
output_tensor = GptImageClient._pil_list_to_tensor(all_pil_images)
# ── 8. 完成日志 ───────────────────────────────────────────────────────
elapsed = time.time() - start_time elapsed = time.time() - start_time
print( print(
f"[o1key GPT Image] 完成!耗时 {elapsed:.1f}s" f"[o1key GPT Image] 完成!耗时 {elapsed:.1f}s"