qFelix's picture
Raise warm-call timeouts (text 8->20s, FLUX 12->30s) so the hosted Space gets real AI + comics instead of cold-start fallbacks
dd44446
Raw
History Blame Contribute Delete
2.57 kB
"""Modal FLUX client for comic-panel image generation.
If FLUX_URL is set, POSTs the image prompt to the Modal FLUX endpoint and
returns a base64 PNG. Otherwise β€” or on any failure/timeout β€” returns None,
and the frontend renders the composed chibi-comic fallback instead. Mirrors the
warm / cold-timeout pattern in llm.py.
"""
from __future__ import annotations
import os
import threading
import time
import requests
from .trace import trace
# 30s warm default: on a hosted Space a slightly-cold / loaded FLUX container
# needs more than 12s, else panels silently no-op. Warm renders are ~3-6s.
FLUX_TIMEOUT = float(os.environ.get("BDS_FLUX_TIMEOUT", "30"))
FLUX_COLD_TIMEOUT = float(os.environ.get("BDS_FLUX_COLD_TIMEOUT", "90"))
_warmed = False
def flux_available() -> bool:
return bool(os.environ.get("FLUX_URL"))
def _headers() -> dict:
return {"Authorization": "Bearer " + os.environ.get("FLUX_TOKEN", "")}
def warm() -> None:
"""Boot the FLUX container in the background (page load / new game)."""
if not flux_available() or _warmed:
return
threading.Thread(target=_warm_ping, daemon=True).start()
def _warm_ping() -> None:
try:
requests.post(os.environ["FLUX_URL"], json={"warmup": True},
headers=_headers(), timeout=FLUX_COLD_TIMEOUT + 60)
trace("flux", "warmup ping done")
except requests.RequestException:
pass
def generate_comic(prompt: str) -> str | None:
"""Return a base64 PNG for the comic, or None β†’ composed fallback."""
if not flux_available() or not prompt:
return None
global _warmed
timeout = FLUX_TIMEOUT if _warmed else FLUX_COLD_TIMEOUT
t0 = time.time()
try:
resp = requests.post(os.environ["FLUX_URL"], json={"prompt": prompt},
headers=_headers(), timeout=timeout)
ms = int((time.time() - t0) * 1000)
if resp.status_code == 200:
body = resp.json()
img = body.get("image_b64")
if img:
_warmed = True
trace("flux", f"comic LIVE {ms}ms ({len(img)}b)")
return img
trace("flux", f"comic not-ok {ms}ms: "
f"{str(body.get('error', body))[:120]}")
else:
trace("flux", f"comic HTTP {resp.status_code} {ms}ms")
except requests.Timeout:
trace("flux", f"comic TIMEOUT after {timeout}s")
except requests.RequestException as exc:
trace("flux", f"comic transport error: {str(exc)[:120]}")
return None