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:
co-authored by
Claude Sonnet 4.5
parent
afa732b93a
commit
949f7bb180
@@ -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):
|
||||||
|
|||||||
+66
-19
@@ -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,12 +141,22 @@ class O1keyGPTImage:
|
|||||||
raise ValueError("未授权!") from None
|
raise ValueError("未授权!") from None
|
||||||
raise
|
raise
|
||||||
|
|
||||||
# ── 3. 调用 API ───────────────────────────────────────────────────────
|
# ── 4. 解析批量提示词 ─────────────────────────────────────────────────
|
||||||
|
batch_prompts = parse_batch_prompts(prompt)
|
||||||
|
|
||||||
|
# ── 5. 调用 API ───────────────────────────────────────────────────────
|
||||||
|
all_pil_images = []
|
||||||
|
|
||||||
|
if batch_prompts:
|
||||||
|
# 批量模式:逐条提示词调用
|
||||||
|
total = len(batch_prompts)
|
||||||
|
print(f"[o1key GPT Image] 批量模式 | {total} 条提示词 | 每条生成 {生图数量} 张")
|
||||||
|
for idx, p in enumerate(batch_prompts, 1):
|
||||||
try:
|
try:
|
||||||
pil_images = client.run_sync(
|
pil_images = client.run_sync(
|
||||||
prompt=prompt,
|
prompt=p,
|
||||||
model=模型,
|
model=模型,
|
||||||
quality="low",
|
quality=quality,
|
||||||
background="auto",
|
background="auto",
|
||||||
size=size,
|
size=size,
|
||||||
n=生图数量,
|
n=生图数量,
|
||||||
@@ -145,15 +164,43 @@ class O1keyGPTImage:
|
|||||||
image_tensor=图片,
|
image_tensor=图片,
|
||||||
mask_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:
|
except Exception as e:
|
||||||
error_msg = str(e).split('\n')[0]
|
error_msg = str(e).split('\n')[0]
|
||||||
print(f"[o1key GPT Image] ❌ {error_msg}")
|
print(f"[o1key GPT Image] ❌ {error_msg}")
|
||||||
raise RuntimeError(error_msg) from None
|
raise RuntimeError(error_msg) from None
|
||||||
|
|
||||||
# ── 4. PIL → tensor ───────────────────────────────────────────────────
|
# ── 6. 检查是否有可用图像 ─────────────────────────────────────────────
|
||||||
output_tensor = GptImageClient._pil_list_to_tensor(pil_images)
|
if not all_pil_images:
|
||||||
|
raise RuntimeError("所有提示词均生成失败,无可用图像输出")
|
||||||
|
|
||||||
# ── 5. 完成日志 ───────────────────────────────────────────────────────
|
# ── 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,"
|
||||||
|
|||||||
Reference in New Issue
Block a user