Spaces:
Running
Running
Download src/agents/image_agent.py from Brettapps/email-assistant: direct link, hf CLI and curl.
- Browser
- Download file 5.41 kB
-
https://huggingface.co/spaces/Brettapps/email-assistant/resolve/main/src/agents/image_agent.py
- Command line
-
hf download hf://spaces/Brettapps/email-assistant/src/agents/image_agent.py
-
curl -L -o image_agent.py https://huggingface.co/spaces/Brettapps/email-assistant/resolve/main/src/agents/image_agent.py
5.41 kB
| #!/usr/bin/env python3 | |
| """ | |
| Image Agent — generates images via HuggingFace Inference API. | |
| Routes image-generation requests from the email inbox through | |
| FLUX.1-schnell (fast, free tier) to the HF Inference API. | |
| Saves generated images to the Obsidian vault under Email/Images/. | |
| """ | |
| from __future__ import annotations | |
| import json, os, re, datetime, io, base64, sys | |
| from pathlib import Path | |
| from typing import Any | |
| import httpx | |
| CONFIG_PATH = Path(__file__).resolve().parents[2] / "config" / "email.json" | |
| HF_TOKEN = os.getenv("HF_TOKEN", "") or os.getenv("HF_ACCESS_TOKEN", "") | |
| HF_API_URL = "https://api-inference.huggingface.co/models/black-forest-labs/FLUX.1-schnell" | |
| OBSIDIAN_VAULT = Path(os.getenv("OBSIDIAN_VAULT", "/tmp/email_vault")) | |
| IMAGE_DIR = OBSIDIAN_VAULT / "Email" / "Images" | |
| def load_config() -> dict: | |
| with open(CONFIG_PATH) as f: | |
| return json.load(f) | |
| def generate_image(prompt: str, width: int = 1024, height: int = 1024, | |
| num_steps: int = 4, guidance_scale: float = 0.0) -> dict[str, Any]: | |
| """Generate an image via HF Inference API.""" | |
| if not HF_TOKEN: | |
| return {"error": "HF_TOKEN not set"} | |
| headers = {"Authorization": f"Bearer {HF_TOKEN}"} | |
| payload = { | |
| "inputs": prompt, | |
| "parameters": { | |
| "num_inference_steps": num_steps, | |
| "guidance_scale": guidance_scale, | |
| "width": width, | |
| "height": height, | |
| "max_sequence_length": 512, | |
| }, | |
| } | |
| try: | |
| with httpx.Client(timeout=120, headers=headers) as client: | |
| resp = client.post(HF_API_URL, json=payload) | |
| if resp.status_code != 200: | |
| return {"error": f"HF API {resp.status_code}: {resp.text[:200]}"} | |
| # HF returns image bytes for image-generation tasks | |
| img_bytes = resp.content | |
| if not img_bytes: | |
| return {"error": "Empty response from HF"} | |
| except httpx.ConnectError as e: | |
| return {"error": f"Connection failed: {str(e)}"} | |
| except httpx.TimeoutException: | |
| return {"error": "HF API request timed out"} | |
| except Exception as e: | |
| return {"error": f"Request failed: {str(e)}"} | |
| # Save to Obsidian vault | |
| IMAGE_DIR.mkdir(parents=True, exist_ok=True) | |
| ts = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") | |
| safe_prompt = re.sub(r"[^\w\s-]", "", prompt)[:40].replace(" ", "_") | |
| fname = f"{ts}_{safe_prompt}.png" | |
| fpath = IMAGE_DIR / fname | |
| with open(fpath, "wb") as f: | |
| f.write(img_bytes) | |
| # Also save as base64 for markdown embedding | |
| b64 = base64.b64encode(img_bytes).decode() | |
| # Write metadata | |
| meta = { | |
| "prompt": prompt, | |
| "width": width, | |
| "height": height, | |
| "steps": num_steps, | |
| "guidance_scale": guidance_scale, | |
| "file": str(fpath), | |
| "size_bytes": len(img_bytes), | |
| "timestamp": datetime.datetime.now().isoformat(), | |
| } | |
| meta_path = IMAGE_DIR / f"{ts}_{safe_prompt}.json" | |
| with open(meta_path, "w") as f: | |
| json.dump(meta, f, indent=2) | |
| return { | |
| "status": "success", | |
| "prompt": prompt, | |
| "file": str(fpath), | |
| "size_bytes": len(img_bytes), | |
| "base64_preview": b64[:200] + "...", | |
| "metadata": meta, | |
| } | |
| def generate_from_email(email_data: dict) -> dict[str, Any]: | |
| """Process an email requesting image generation.""" | |
| subject = email_data.get("subject", "") | |
| body = email_data.get("body", "") | |
| sender = email_data.get("sender", "") | |
| # Extract image prompt from email body | |
| # Look for explicit prompt markers | |
| prompt_match = re.search( | |
| r"(?:image|picture|generate|create|draw|make)\s*:?\s*(.+?)(?:\n|$|(?:\.|!|\?)$)", | |
| body, re.IGNORECASE, | |
| ) | |
| if prompt_match: | |
| prompt = prompt_match.group(1).strip().rstrip(".") | |
| else: | |
| # Use subject + first line of body as prompt | |
| prompt = f"{subject}. {body[:200]}" | |
| result = generate_image(prompt) | |
| # Store in vault with email context | |
| if "error" not in result: | |
| vault_entry = { | |
| "source_email": { | |
| "subject": subject, | |
| "sender": sender, | |
| "date": email_data.get("date", ""), | |
| }, | |
| "image_result": result, | |
| } | |
| meta_path = Path(result["file"]).with_suffix(".json") | |
| with open(meta_path, "w") as f: | |
| json.dump(vault_entry, f, indent=2) | |
| return result | |
| def list_generated_images(limit: int = 20) -> list[dict]: | |
| """List generated images from the vault.""" | |
| images = [] | |
| if not IMAGE_DIR.exists(): | |
| return images | |
| for f in sorted(IMAGE_DIR.glob("*.png"), reverse=True)[:limit]: | |
| meta_path = f.with_suffix(".json") | |
| meta = {} | |
| if meta_path.exists(): | |
| with open(meta_path) as mf: | |
| meta = json.load(mf) | |
| images.append({ | |
| "file": f.name, | |
| "size_bytes": f.stat().st_size, | |
| "metadata": meta, | |
| }) | |
| return images | |
| if __name__ == "__main__": | |
| # CLI test mode | |
| if len(sys.argv) > 1: | |
| result = generate_image(sys.argv[1]) | |
| print(json.dumps(result, indent=2, default=str)) | |
| else: | |
| print(json.dumps(list_generated_images(), indent=2)) |