File size: 10,417 Bytes
94ecb74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
"""
🎨 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'<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]}...")

    # 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)