AI Image Generator — Python source
Turn a text prompt into a 1024×1024 image with FLUX.1 [schnell] on Cloudflare Workers AI. No account, no API key — rate-limited for fair use. Your prompt goes to the model through our Worker; we store no prompts and no images.
This is the Python implementation — the same logic the interactive tool runs, in a shareable, citable form.
"""AI Image Generator — prompt validation, usage-gate verdicts, and ?prompt=
URL state for FLUX.1-schnell image generation.
Language: Python (3.9+, standard library only)
Source: CosmoDev polyglot showcase port of the AI Image Generator tool,
from src/lib/image-gen.ts (the canonical TypeScript implementation).
"""
from __future__ import annotations
import math
import re
from datetime import datetime, timedelta, timezone
from urllib.parse import parse_qs, quote, urlencode
MODEL = "@cf/black-forest-labs/flux-1-schnell"
STEPS = 4 # flux-1-schnell's design point -> 57.6 neurons/image
PROMPT_MAX_CHARS = 600 # UX clamp, below the model's 2048 limit
IP_HOUR_CAP = 10
DAILY_CAP = 100
# Control chars except \n: C0 minus U+000A, DEL, and C1.
_CONTROL_CHARS = re.compile(r"[\x00-\x09\x0B-\x1F\x7F-\x9F]")
def validate_prompt(raw: str) -> tuple[str | None, str | None]:
"""Return (prompt, None) on success or (None, reason) where reason is
'empty' | 'tooLong' | 'controlChars'."""
prompt = re.sub(r"\r\n?", "\n", raw).strip() # normalize line endings first
if not prompt:
return None, "empty"
if len(prompt) > PROMPT_MAX_CHARS:
return None, "tooLong"
if _CONTROL_CHARS.search(prompt):
return None, "controlChars"
return prompt, None
def minutes_to_utc_midnight(now: datetime) -> int:
"""Minutes (>= 1) until the next UTC midnight — the daily window reset."""
utc = now.astimezone(timezone.utc)
midnight = datetime(utc.year, utc.month, utc.day, tzinfo=timezone.utc) + timedelta(days=1)
return max(1, math.ceil((midnight - utc).total_seconds() / 60))
def check_gate(
ip_count: int, daily_count: int, now: datetime | None = None
) -> dict:
"""Daily cap wins: the site-global budget resets at UTC midnight."""
if daily_count >= DAILY_CAP:
return {
"ok": False,
"error": "RATE_LIMITED_DAILY",
"retryAfterMinutes": minutes_to_utc_midnight(now or datetime.now(timezone.utc)),
}
if ip_count >= IP_HOUR_CAP:
return {"ok": False, "error": "RATE_LIMITED_IP", "retryAfterMinutes": 60}
return {"ok": True}
def encode_url_state(prompt: str) -> str:
"""The shareable state: '' when there is nothing to share. quote_via=quote
gives encodeURIComponent-style %20 (not form-style +) for spaces."""
if not prompt:
return ""
return "?prompt=" + urlencode({"prompt": prompt}, quote_via=quote)
def decode_url_state(search: str) -> str:
"""Inverse of encode_url_state — a missing param decodes to ''."""
return parse_qs(search.lstrip("?")).get("prompt", [""])[0]
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →