#!/usr/bin/env python3
"""Ideogram精华盲审 · 三引擎同题渲染 (Ks / Z-Image / FLUX.2)
统一: S1 prompt 原文 + seed 9909 + 1360x768; 各臂用各自0905/0906验证过的现役配方
取图走 /view API (0908教训: docker cp 在 output 重向后是死路)
"""
import json, time, urllib.request, os, sys

BASE = "http://127.0.0.1:8190"  # secondary/GPU2; :8188 被 ab_fasth3 批渲染占用勿插队
OUT = "/home/kai/shared/2026-09-09/ideogram_blind4"
os.makedirs(OUT, exist_ok=True)
SEED = 9909  # 与 S1_cafe.png 同 seed

PROMPT = ("A cinematic photorealistic interior of a cozy late-night coffee shop, "
          "warm amber Edison bulb lighting, a glowing neon sign on the brick wall reading \"龙轩咖啡\", "
          "espresso machine on wooden counter, rain-streaked windows with bokeh city lights outside, "
          "empty seats by the window, rich film grain, 35mm photography, moody atmosphere")

ARMS = {
  "ks": {
    "nodes": lambda: {
      "5": {"class_type": "UNETLoader", "inputs": {"unet_name": "krea2_turbo_bf16.safetensors", "weight_dtype": "default"}},
      "6": {"class_type": "CLIPLoader", "inputs": {"clip_name": "qwen3vl_4b_fp8_scaled.safetensors", "type": "krea2", "device": "default"}},
      "7": {"class_type": "VAELoader", "inputs": {"vae_name": "qwen_image_vae.safetensors"}},
      "9": {"class_type": "CLIPTextEncode", "inputs": {"clip": ["6", 0], "text": PROMPT}},
      "10": {"class_type": "CLIPTextEncode", "inputs": {"clip": ["6", 0], "text": "blurry ugly bad, low quality, watermark"}},
      "13": {"class_type": "EmptySD3LatentImage", "inputs": {"width": 1360, "height": 768, "batch_size": 1}},
      "3": {"class_type": "KSampler", "inputs": {"model": ["5", 0], "seed": SEED, "steps": 8, "cfg": 1.0,
            "sampler_name": "euler", "scheduler": "simple", "positive": ["9", 0], "negative": ["10", 0],
            "latent_image": ["13", 0], "denoise": 1.0}},
      "8": {"class_type": "VAEDecode", "inputs": {"samples": ["3", 0], "vae": ["7", 0]}},
      "17": {"class_type": "SaveImage", "inputs": {"images": ["8", 0], "filename_prefix": "bld4/ks"}},
    },
    "timeout": 300,
  },
  "zimg": {
    "nodes": lambda: {
      "5": {"class_type": "UNETLoader", "inputs": {"unet_name": "z_image_turbo_bf16.safetensors", "weight_dtype": "default"}},
      "6": {"class_type": "CLIPLoader", "inputs": {"clip_name": "qwen_3_4b_fp8_mixed.safetensors", "type": "lumina2", "device": "default"}},
      "7": {"class_type": "VAELoader", "inputs": {"vae_name": "z_image_ae.safetensors"}},
      "9": {"class_type": "CLIPTextEncode", "inputs": {"clip": ["6", 0], "text": PROMPT}},
      "10": {"class_type": "CLIPTextEncode", "inputs": {"clip": ["6", 0], "text": "blurry ugly bad, low quality, watermark"}},
      "13": {"class_type": "EmptySD3LatentImage", "inputs": {"width": 1360, "height": 768, "batch_size": 1}},
      "3": {"class_type": "KSampler", "inputs": {"model": ["5", 0], "seed": SEED, "steps": 9, "cfg": 1.0,
            "sampler_name": "euler", "scheduler": "simple", "positive": ["9", 0], "negative": ["10", 0],
            "latent_image": ["13", 0], "denoise": 1.0}},
      "8": {"class_type": "VAEDecode", "inputs": {"samples": ["3", 0], "vae": ["7", 0]}},
      "17": {"class_type": "SaveImage", "inputs": {"images": ["8", 0], "filename_prefix": "bld4/zimg"}},
    },
    "timeout": 300,
  },
  "fx2": {
    "nodes": lambda: {
      "10": {"class_type": "OTUNetLoaderW8A8", "inputs": {"unet_name": "flux2_dev_fp8mixed.safetensors", "weight_dtype": "default",
             "model_type": "flux2", "on_the_fly_quantization": True, "enable_convrot": True, "lora_mode": "None"}},
      "11": {"class_type": "CLIPLoader", "inputs": {"clip_name": "mistral_3_small_flux2_fp8.safetensors", "type": "flux2"}},
      "12": {"class_type": "VAELoader", "inputs": {"vae_name": "flux2-vae.safetensors"}},
      "6": {"class_type": "CLIPTextEncode", "inputs": {"clip": ["11", 0], "text": PROMPT}},
      "7": {"class_type": "CLIPTextEncode", "inputs": {"clip": ["11", 0], "text": "text, watermark, low quality, blurry, distorted, deformed, ugly, oversaturated, cartoon, anime"}},
      "5": {"class_type": "EmptyLatentImage", "inputs": {"width": 1360, "height": 768, "batch_size": 1}},
      "3": {"class_type": "KSampler", "inputs": {"seed": SEED, "steps": 24, "cfg": 3.5, "sampler_name": "dpmpp_2m_sde",
            "scheduler": "karras", "denoise": 1.0, "model": ["10", 0], "positive": ["6", 0], "negative": ["7", 0],
            "latent_image": ["5", 0]}},
      "13": {"class_type": "VAEDecode", "inputs": {"samples": ["3", 0], "vae": ["12", 0]}},
      "22": {"class_type": "SaveImage", "inputs": {"images": ["13", 0], "filename_prefix": "bld4/fx2"}},
    },
    "timeout": 900,
  },
}

def post(url, payload, timeout=60):
    req = urllib.request.Request(url, data=json.dumps(payload).encode(), headers={"Content-Type": "application/json"})
    return json.loads(urllib.request.urlopen(req, timeout=timeout).read())

def get(url, timeout=30):
    return json.loads(urllib.request.urlopen(url, timeout=timeout).read())

def render(arm, cfg):
    t0 = time.time()
    pid = post(BASE + "/prompt", {"prompt": cfg["nodes"](), "client_id": "bld4"})["prompt_id"]
    print(f"[{arm}] submitted {pid}", flush=True)
    deadline = t0 + cfg["timeout"]
    while time.time() < deadline:
        time.sleep(5)
        h = get(f"{BASE}/history/{pid}")
        if pid not in h:
            continue
        st = h[pid].get("status", {})
        if st.get("status_str") == "success":
            for node_out in h[pid].get("outputs", {}).values():
                for im in node_out.get("images", []):
                    dur = round(time.time() - t0, 1)
                    # /view 取图
                    vurl = f"{BASE}/view?filename={urllib.request.quote(im['filename'])}&subfolder={urllib.request.quote(im.get('subfolder',''))}&type=output"
                    data = urllib.request.urlopen(vurl, timeout=60).read()
                    out_png = f"{OUT}/{arm}.png"
                    open(out_png, "wb").write(data)
                    print(f"[{arm}] DONE {dur}s -> {out_png} ({len(data)//1024}KB)", flush=True)
                    return dur
            return None
        if st.get("status_str") == "error":
            errs = [json.dumps(m[1], ensure_ascii=False)[:300] for m in st.get("messages", []) if m[0] == "execution_error"]
            print(f"[{arm}] ERROR: {errs}", flush=True)
            return None
    print(f"[{arm}] TIMEOUT", flush=True)
    return None

if __name__ == "__main__":
    q = get(BASE + "/queue")
    print("queue before:", {k: len(v) for k, v in q.items()}, flush=True)
    timing = {}
    for arm in ["ks", "zimg", "fx2"]:
        d = render(arm, ARMS[arm])
        timing[arm] = d
        json.dump(timing, open(f"{OUT}/render_timing.json", "w"), ensure_ascii=False, indent=1)
    print("ALL:", timing, flush=True)
    sys.exit(0 if all(v for v in timing.values()) else 1)
