"""Pollinations.ai image generation backend — free, no API key required. Generates images via https://image.pollinations.ai/prompt/{prompt}. Downloads the result and saves to $HERMES_HOME/cache/images/. Supports landscape/square/portrait aspect ratios. """ from __future__ import annotations import logging import os import urllib.parse import urllib.request from typing import Any, Dict, List from agent.image_gen_provider import ( DEFAULT_ASPECT_RATIO, ImageGenProvider, error_response, resolve_aspect_ratio, success_response, ) logger = logging.getLogger(__name__) _API_BASE = "https://image.pollinations.ai/prompt" _SIZES = { "landscape": "1344x768", "square": "1024x1024", "portrait": "768x1344", } DEFAULT_MODEL = "flux" class PollinationsImageGenProvider(ImageGenProvider): """Pollinations.ai free image generation backend.""" @property def name(self) -> str: return "pollinations" @property def display_name(self) -> str: return "Pollinations (Free)" def is_available(self) -> bool: return True # no API key needed def list_models(self) -> List[Dict[str, Any]]: return [ { "id": "flux", "display": "Flux (Free)", "speed": "~10-20s", "strengths": "Free, no API key, good quality", "price": "Free", } ] def get_setup_schema(self) -> Dict[str, Any]: return { "name": "Pollinations.ai", "badge": "free", "tag": "Free image generation — no API key required", "env_vars": [], } def default_model(self) -> str: return DEFAULT_MODEL def generate( self, prompt: str, aspect_ratio: str = DEFAULT_ASPECT_RATIO, **kwargs: Any, ) -> Dict[str, Any]: prompt = (prompt or "").strip() aspect = resolve_aspect_ratio(aspect_ratio) if not prompt: return error_response( error="Prompt is required", error_type="invalid_argument", provider="pollinations", aspect_ratio=aspect, ) width, height = _SIZES.get(aspect, _SIZES["square"]).split("x") seed = os.urandom(4).hex() url = f"{_API_BASE}/{urllib.parse.quote(prompt)}?width={width}&height={height}&seed={seed}&nologo=true&model=flux" # Download image to cache try: from hermes_constants import get_hermes_home import datetime import uuid cache_dir = get_hermes_home() / "cache" / "images" cache_dir.mkdir(parents=True, exist_ok=True) ts = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") short = uuid.uuid4().hex[:8] save_path = cache_dir / f"pollinations_{ts}_{short}.png" req = urllib.request.Request(url, headers={"User-Agent": "Hermes/1.0"}) with urllib.request.urlopen(req, timeout=60) as resp: data = resp.read() save_path.write_bytes(data) image_ref = str(save_path) except Exception as exc: logger.warning("Pollinations image download failed: %s", exc) # Fallback: return URL directly image_ref = url return success_response( image=image_ref, model=DEFAULT_MODEL, prompt=prompt, aspect_ratio=aspect, provider="pollinations", extra={"url": url}, ) def register(ctx) -> None: ctx.register_image_gen_provider(PollinationsImageGenProvider())