Files

205 lines
7.1 KiB
Python

import base64
import time
import comfy.model_management as mm
import requests
import torch
from .IMAGE_GrokEdit_apikey import (
ASPECT_RATIOS,
BASE_URL,
MODELS,
QUALITY_MODELS,
request_with_interrupt,
send_status,
)
from ._image_gpt_common import (
empty_image,
format_credits_value,
format_rmb_value,
load_image_to_tensor,
usd_to_comfy_credits,
)
GENERATION_ENDPOINT = "/proxy/xai/v1/images/generations"
def estimate_price(model, resolution, quality, number_of_images):
is_1k = resolution.lower() == "1k"
if model == "grok-imagine-image-2.0":
rate = (0.04 if is_1k else 0.06) if quality == "low" else (0.06 if is_1k else 0.08)
elif model == "grok-imagine-image-quality":
rate = 0.05 if is_1k else 0.07
elif model == "grok-imagine-image-pro":
rate = 0.07
else:
rate = 0.02
return rate * number_of_images
def format_price(result, response, model, resolution, quality, number_of_images):
usage = result.get("usage") or {}
ticks = usage.get("cost_in_usd_ticks")
credits_header = response.headers.get("X-Comfy-Credits-Used")
credits = None
try:
credits = float(credits_header) if credits_header is not None else None
except ValueError:
pass
usd = ticks / 10_000_000_000 if isinstance(ticks, (int, float)) else None
if usd is None and credits is not None:
usd = credits / usd_to_comfy_credits(1.0)
if credits is None and usd is not None:
credits = usd_to_comfy_credits(usd)
if usd is None:
usd = estimate_price(model, resolution, quality, number_of_images)
return "\n".join([
f"Estimated price (USD): ${usd:.6f}",
f"Estimated price (RMB): {format_rmb_value(usd)}",
f"Estimated price (Credits): {format_credits_value(usd)}",
])
lines = [
f"Actual price (USD): ${usd:.6f}",
f"Actual price (RMB): {format_rmb_value(usd)}",
]
if credits is not None:
lines.append(f"Credits used: {credits:.2f} credits")
return "\n".join(lines)
class PDGrokImageAPIKey:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"api_key": ("STRING", {
"default": "",
"multiline": False,
"placeholder": "Your ComfyUI API Key",
}),
"prompt": ("STRING", {"default": "", "multiline": True}),
"model": (MODELS, {"default": "grok-imagine-image-2.0"}),
"aspect_ratio": (ASPECT_RATIOS[1:], {"default": "1:1"}),
"resolution": (["1K", "2K"], {"default": "1K"}),
"quality": (["medium", "low"], {"default": "medium"}),
"number_of_images": ("INT", {"default": 1, "min": 1, "max": 10, "step": 1}),
"seed": ("INT", {"default": 0, "min": 0, "max": 2147483647, "step": 1}),
},
"hidden": {
"unique_id": "UNIQUE_ID",
},
}
RETURN_TYPES = ("IMAGE", "STRING")
RETURN_NAMES = ("image", "show_price")
FUNCTION = "generate_image"
CATEGORY = "PD_Tools/Image_Generation"
def generate_image(
self,
api_key,
prompt,
model,
aspect_ratio,
resolution,
quality,
number_of_images,
seed,
unique_id,
):
if not api_key or not api_key.strip():
return (empty_image(), "Error: ComfyUI API Key is required")
if not prompt or not prompt.strip():
return (empty_image(), "Error: prompt is required")
payload = {
"model": model,
"prompt": prompt.strip(),
"aspect_ratio": aspect_ratio,
"n": number_of_images,
"seed": seed,
"response_format": "url",
"resolution": resolution.lower(),
"quality": quality if model in QUALITY_MODELS else None,
}
headers = {
"X-API-KEY": api_key.strip(),
"Accept": "application/json",
"Content-Type": "application/json",
"User-Agent": "ComfyUI-PD-Node/2.1",
}
start_time = time.monotonic()
session = requests.Session()
try:
response = request_with_interrupt(
session,
session.post,
f"{BASE_URL}{GENERATION_ENDPOINT}",
unique_id,
"Waiting for Grok server",
start_time,
headers=headers,
json=payload,
timeout=600,
)
if response.status_code != 200:
send_status(unique_id, f"Failed\nTime elapsed: {int(time.monotonic() - start_time)}s")
return (empty_image(), f"API Error {response.status_code}: {response.text[:500]}")
result = response.json()
output_images = []
for item in result.get("data") or []:
if item.get("url"):
download = request_with_interrupt(
session,
session.get,
item["url"],
unique_id,
"Downloading result",
start_time,
timeout=120,
)
download.raise_for_status()
output_images.append(load_image_to_tensor(download.content))
elif item.get("b64_json"):
output_images.append(load_image_to_tensor(base64.b64decode(item["b64_json"])))
if not output_images:
send_status(unique_id, f"Failed\nTime elapsed: {int(time.monotonic() - start_time)}s")
return (empty_image(), f"Error: response contained no images: {str(result)[:500]}")
image = torch.cat(output_images, dim=0)
price = format_price(result, response, model, resolution, quality, number_of_images)
elapsed = time.monotonic() - start_time
send_status(unique_id, f"Completed\nTime elapsed: {int(elapsed)}s")
print(f"[PD_GROK_IMAGE] {model} completed {len(output_images)} image(s) in {elapsed:.2f}s")
return (image, price)
except mm.InterruptProcessingException:
raise
except requests.exceptions.Timeout:
send_status(unique_id, f"Timed out\nTime elapsed: {int(time.monotonic() - start_time)}s")
return (empty_image(), "Error: request timeout (600s)")
except requests.exceptions.ConnectionError as error:
send_status(unique_id, f"Connection failed\nTime elapsed: {int(time.monotonic() - start_time)}s")
return (empty_image(), f"Error: connection failed - {error}")
except Exception as error:
send_status(unique_id, f"Failed\nTime elapsed: {int(time.monotonic() - start_time)}s")
return (empty_image(), f"Error: {error}")
finally:
session.close()
NODE_CLASS_MAPPINGS = {
"PDGrokImageAPIKey": PDGrokImageAPIKey,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"PDGrokImageAPIKey": "PD-grok-text to image",
}