|
""" |
|
π¨ 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__) |
|
|
|
|
|
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() |
|
|
|
|
|
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" |
|
} |
|
|
|
|
|
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]}...") |
|
|
|
|
|
params = {**self.default_params, **kwargs} |
|
|
|
|
|
encoded_prompt = urllib.parse.quote(prompt) |
|
url = f"{self.base_url}/{encoded_prompt}" |
|
|
|
print(f"π Request URL: {url}") |
|
print(f"π Parameters: {params}") |
|
|
|
|
|
import requests |
|
|
|
try: |
|
response = requests.get(url, params=params, timeout=300) |
|
response.raise_for_status() |
|
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 |
|
|
|
|
|
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: |
|
|
|
import random |
|
|
|
|
|
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" |
|
] |
|
|
|
|
|
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}") |
|
|
|
|
|
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 |
|
|
|
|
|
used_images = set() |
|
|
|
|
|
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]}...") |
|
|
|
|
|
best_match = None |
|
for i, image in enumerate(images): |
|
if i in used_images: |
|
continue |
|
|
|
if not image or "data_url" not in image: |
|
continue |
|
|
|
image_lesson = image.get("lesson_title", "") |
|
image_desc = image.get("placeholder_description", "") |
|
|
|
|
|
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) |
|
|
|
|
|
specific_placeholder = f"{{{{IMAGE_PLACEHOLDER:{placeholder_lesson}:{placeholder_desc}}}}}" |
|
description = image.get("prompt", image.get("placeholder_description", "Educational image")) |
|
|
|
img_html = f'<img src="{image["data_url"]}" alt="{description}" style="max-width: 100%; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.3); border: 2px solid #4a4a7a; margin: 1rem 0;">' |
|
|
|
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]}...") |
|
|
|
|
|
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]: |
|
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) |
|
|
|
|
|
|
|
_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) |