""" 🎨 Image Generation Tools High-quality image generation using Pollinations API with gptimage model """ import urllib.parse import base64 from typing import Dict, Any, Optional, List import logging logger = logging.getLogger(__name__) # Import config for Pollinations settings from ..utils.config import config class ImageGenerator: """High-quality image generation using Pollinations API""" def __init__(self): self.base_url = "https://image.pollinations.ai/prompt" self.image_config = config.get_image_generation_config() # Set up default parameters from config self.default_params = { "width": self.image_config.get("default_width", 1280), "height": self.image_config.get("default_height", 720), "model": self.image_config.get("default_model", "gptimage"), "nologo": "true", "enhance": "true", "private": "true" } # Add API credentials if available if self.image_config.get("pollinations_api_token"): self.default_params["token"] = self.image_config["pollinations_api_token"] if self.image_config.get("pollinations_api_reference"): self.default_params["referrer"] = self.image_config["pollinations_api_reference"] async def generate_image_url(self, prompt: str, **kwargs) -> Optional[str]: """Generate image and return URL""" try: print(f"🎨 Generating image: {prompt[:50]}...") # Merge custom params with defaults params = {**self.default_params, **kwargs} # Encode the prompt for URL encoded_prompt = urllib.parse.quote(prompt) url = f"{self.base_url}/{encoded_prompt}" print(f"🌐 Request URL: {url}") print(f"📋 Parameters: {params}") # Use requests instead of aiohttp for better compatibility import requests try: response = requests.get(url, params=params, timeout=300) response.raise_for_status() # Raise an exception for bad status codes except requests.exceptions.RequestException as e: print(f"❌ Request failed: {e}") if hasattr(e, 'response') and e.response is not None: print(f"📄 Error response: {e.response.text[:200]}...") return None if response.status_code == 200: image_data = response.content # Convert to base64 base64_data = base64.b64encode(image_data).decode('utf-8') print(f"✅ Image data generated successfully ({len(image_data)} bytes)") return { "prompt": prompt, "base64_data": base64_data, "data_url": f"data:image/jpeg;base64,{base64_data}", "size_bytes": len(image_data), "params": params } else: print(f"❌ Image generation failed: HTTP {response.status_code}") print(f"📄 Response text: {response.text[:200]}...") return None except Exception as e: logger.error(f"Image data generation failed: {e}") print(f"❌ Image data generation error: {e}") return None async def generate_educational_image(self, lesson_title: str, topic: str, style: str = "educational") -> Optional[Dict[str, Any]]: """Generate educational image for a lesson""" try: # Create varied educational prompts with different styles and approaches import random # Add variety with different visual styles and descriptors visual_styles = [ "modern minimalist", "vibrant colorful", "clean professional", "sleek contemporary", "bright engaging", "polished elegant", "dynamic visual", "crisp detailed" ] art_styles = [ "digital illustration", "infographic design", "vector art", "educational diagram", "technical illustration", "conceptual artwork", "instructional graphic", "learning visual" ] descriptors = [ "high-quality", "detailed", "clear and informative", "visually appealing", "educational focused", "learning oriented", "instructionally designed", "pedagogically sound" ] # Randomly select style elements for variety visual_style = random.choice(visual_styles) art_style = random.choice(art_styles) descriptor = random.choice(descriptors) if style == "educational": prompt = f"{descriptor.title()} {art_style} about {topic}. {visual_style} style with engaging visual elements." elif style == "diagram": prompt = f"Technical {art_style} showing {topic} concepts. {visual_style} design with clear visual hierarchy and {descriptor} presentation." elif style == "concept": prompt = f"Conceptual {art_style} of {topic}. {visual_style} visual representation with {descriptor} design elements." else: prompt = f"Professional {art_style} about {topic}. {visual_style}, {descriptor} educational style." print(f"🎓 Generating educational image for: {lesson_title}") # Use default parameters from config (already includes API credentials) result = await self.generate_image_url(prompt) if result: result.update({ "lesson_title": lesson_title, "topic": topic, "style": style, "educational": True }) print(f"✅ Educational image generated for: {lesson_title}") return result except Exception as e: logger.error(f"Educational image generation failed: {e}") print(f"❌ Educational image generation error: {e}") return None @staticmethod def create_image_placeholder(lesson_title: str, description: str) -> str: """Create an image placeholder marker for lessons""" return f"{{{{IMAGE_PLACEHOLDER:{lesson_title}:{description}}}}}" def extract_image_placeholders(content: str) -> List[Dict[str, str]]: """Extract image placeholder markers from content""" import re pattern = r'\{\{IMAGE_PLACEHOLDER:([^:]+):([^}]+)\}\}' matches = re.findall(pattern, content) return [ { "lesson_title": match[0], "description": match[1], "placeholder": f"{{{{IMAGE_PLACEHOLDER:{match[0]}:{match[1]}}}}}" } for match in matches ] def replace_image_placeholders(content: str, images: List[Dict[str, Any]]) -> str: """Replace image placeholders with actual image HTML""" print(f"🔄 Replacing image placeholders in content ({len(images)} images available)") if not images: print("⚠️ No images provided for placeholder replacement") return content import re # Track which images have been used to avoid duplication used_images = set() # Find all placeholders in content placeholder_pattern = r'\{\{IMAGE_PLACEHOLDER:([^:]+):([^}]+)\}\}' placeholders = re.findall(placeholder_pattern, content) print(f"🔍 Found {len(placeholders)} total placeholders in content") for placeholder_lesson, placeholder_desc in placeholders: print(f"📝 Looking for image for placeholder: {placeholder_lesson} - {placeholder_desc[:50]}...") # Find the best matching image that hasn't been used best_match = None for i, image in enumerate(images): if i in used_images: continue # Skip already used images if not image or "data_url" not in image: continue image_lesson = image.get("lesson_title", "") image_desc = image.get("placeholder_description", "") # Check if this image matches the placeholder if (image_lesson == placeholder_lesson and (image_desc == placeholder_desc or placeholder_desc in image_desc or image_desc in placeholder_desc)): best_match = (i, image) break if best_match: image_index, image = best_match used_images.add(image_index) # Replace this specific placeholder specific_placeholder = f"{{{{IMAGE_PLACEHOLDER:{placeholder_lesson}:{placeholder_desc}}}}}" description = image.get("prompt", image.get("placeholder_description", "Educational image")) img_html = f'{description}' content = content.replace(specific_placeholder, img_html) print(f"✅ Replaced placeholder with image {image_index+1}: {placeholder_desc[:50]}...") else: print(f"⚠️ No matching image found for: {placeholder_lesson} - {placeholder_desc[:50]}...") # Check if any placeholders remain remaining_placeholders = re.findall(r'\{\{IMAGE_PLACEHOLDER:[^}]+\}\}', content) if remaining_placeholders: print(f"⚠️ {len(remaining_placeholders)} placeholders still remain unreplaced") for placeholder in remaining_placeholders[:3]: # Show first 3 print(f" - {placeholder}") else: print("✅ All image placeholders have been replaced") print(f"📊 Used {len(used_images)} out of {len(images)} available images") return content def create_image_placeholder(lesson_title: str, description: str) -> str: """Convenience function for creating image placeholders""" return ImageGenerator.create_image_placeholder(lesson_title, description) # Global instance for convenience _image_generator = ImageGenerator() async def generate_educational_image(lesson_title: str, topic: str, style: str = "educational") -> Optional[Dict[str, Any]]: """Convenience function for generating educational images""" return await _image_generator.generate_educational_image(lesson_title, topic, style)