Files
comfyui_o1key/nodes/grok_image.py
T
JonyandClaude Opus 4.6 69279c654d feat: 新增 Grok 图像节点、前端 UI 增强、重构 nano-banana 系列
- 新增 Grok Image 节点及客户端
- 新增 save_image_format 节点
- 新增前端 JS 扩展:画笔工具、点阵网格、侧边栏隐藏、资源切换、重命名等
- 重构 nano-banana 节点,移除 pro 版本
- 移除 multi_res_preview 节点
- 新增 http_error 工具模块
- 各客户端和节点优化改进

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-05-24 22:46:55 +08:00

166 lines
5.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
o1key Grok Image 节点
支持 Grok Image / Grok Image Pro 模型的文生图和图生图
"""
import time
from ..clients.grok_image_client import GrokImageClient
from ..utils.image_utils import parse_batch_prompts
from ..utils.config import NETWORK_ROUTE_OPTIONS
try:
from comfy.model_management import processing_interrupted, InterruptProcessingException
_INTERRUPT_AVAILABLE = True
except ImportError:
_INTERRUPT_AVAILABLE = False
processing_interrupted = lambda: False
InterruptProcessingException = RuntimeError
_ASPECT_RATIOS = [
"auto", "1:1", "16:9", "9:16", "4:3", "3:4",
"3:2", "2:3", "2:1", "1:2", "19.5:9", "9:19.5", "20:9", "9:20",
]
class O1keyGrokImage:
@classmethod
def INPUT_TYPES(cls):
optional_inputs = {}
for i in range(1, 4):
optional_inputs[f"参考图{i}"] = ("IMAGE", {
"tooltip": f"Optional reference image {i}",
})
return {
"required": {
"prompt": ("STRING", {
"default": "",
"multiline": True,
"tooltip": "文本提示词,用 --- 独占一行分隔批量提示词",
}),
},
"optional": {
"模型": (["Grok Image", "Grok Image Pro"], {
"default": "Grok Image Pro",
}),
"宽高比": (_ASPECT_RATIOS, {
"default": "auto",
}),
"分辨率": (["1k", "2k"], {
"default": "1k",
}),
"生图数量": ("INT", {
"default": 1,
"min": 1,
"max": 4,
"step": 1,
"display": "number",
}),
"网络线路": (NETWORK_ROUTE_OPTIONS, {
"default": NETWORK_ROUTE_OPTIONS[0],
}),
"seed": ("INT", {
"default": 0,
"min": 0,
"max": 2**31 - 1,
"step": 1,
"display": "number",
"control_after_generate": True,
}),
**optional_inputs,
},
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("IMAGE",)
FUNCTION = "generate"
CATEGORY = "o1key/image"
OUTPUT_NODE = False
def generate(
self,
prompt: str,
模型: str = "Grok Image Pro",
宽高比: str = "auto",
分辨率: str = "1k",
生图数量: int = 1,
网络线路: str = "全球加速",
seed: int = 0,
**kwargs,
):
start_time = time.time()
reference_tensors = []
for i in range(1, 4):
key = f"参考图{i}"
if key in kwargs and kwargs[key] is not None:
reference_tensors.append(kwargs[key])
image_list = reference_tensors if reference_tensors else None
try:
client = GrokImageClient(route=网络线路)
except ValueError as e:
if str(e) == "未授权!":
print("[o1key Grok Image] 请联系作者授权后方可使用!")
raise ValueError("未授权!") from None
raise
try:
batch_prompts = parse_batch_prompts(prompt)
all_pil_images = []
if batch_prompts:
total = len(batch_prompts)
print(f"[o1key Grok Image] 批量模式 | {total} 条提示词 | 每条生成 {生图数量} 张")
for idx, p in enumerate(batch_prompts, 1):
if _INTERRUPT_AVAILABLE and processing_interrupted():
print("[o1key Grok Image] 用户取消")
raise InterruptProcessingException()
try:
pil_images = client.run_sync(
prompt=p, model=模型, aspect_ratio=宽高比,
resolution=分辨率, n=生图数量, image_list=image_list,
)
all_pil_images.extend(pil_images)
snippet = p[:30] + ("..." if len(p) >= 30 else "")
print(f"[o1key Grok Image] [{idx}/{total}] done: {snippet}")
except InterruptProcessingException:
raise
except Exception as e:
error_msg = str(e).split('\n')[0]
snippet = p[:30] + ("..." if len(p) >= 30 else "")
print(f"[o1key Grok Image] [{idx}/{total}] fail: {snippet} → {error_msg}")
else:
if not prompt or not prompt.strip():
raise ValueError("提示词不能为空")
pil_images = client.run_sync(
prompt=prompt, model=模型, aspect_ratio=宽高比,
resolution=分辨率, n=生图数量, image_list=image_list,
)
all_pil_images.extend(pil_images)
if not all_pil_images:
raise RuntimeError("所有提示词均生成失败,无可用图像输出")
output_tensor = GrokImageClient._pil_list_to_tensor(all_pil_images)
elapsed = time.time() - start_time
print(
f"[o1key Grok Image] 完成!耗时 {elapsed:.1f}s,"
f"输出 {output_tensor.shape[0]} 张 "
f"{output_tensor.shape[2]}x{output_tensor.shape[1]}"
)
return (output_tensor,)
finally:
self._print_balance(client)
def _print_balance(self, client):
try:
balance_data = client.query_balance_sync()
balance_info = client.format_balance_info(balance_data)
print(f"[o1key Grok Image] {balance_info}")
except Exception:
pass