Spaces:
Runtime error
Runtime error
| import os | |
| import requests | |
| import streamlit as st | |
| from io import BytesIO | |
| from PIL import Image | |
| from pptx.enum.shapes import MSO_SHAPE | |
| from pptx.enum.shapes import MSO_SHAPE_TYPE | |
| from pptx.dml.color import RGBColor | |
| from pptx.util import Inches, Pt | |
| import random | |
| import json | |
| import base64 | |
| # Define standard icons and shapes that come with python-pptx | |
| STANDARD_SHAPES = { | |
| "rectangle": MSO_SHAPE.RECTANGLE, | |
| "rounded_rectangle": MSO_SHAPE.ROUNDED_RECTANGLE, | |
| "oval": MSO_SHAPE.OVAL, | |
| "triangle": MSO_SHAPE.ISOSCELES_TRIANGLE, | |
| "right_triangle": MSO_SHAPE.RIGHT_TRIANGLE, | |
| "diamond": MSO_SHAPE.DIAMOND, | |
| "pentagon": MSO_SHAPE.PENTAGON, | |
| "hexagon": MSO_SHAPE.HEXAGON, | |
| "heptagon": MSO_SHAPE.HEPTAGON, | |
| "octagon": MSO_SHAPE.OCTAGON, | |
| "star": MSO_SHAPE.STAR_5_POINT, | |
| "arrow": MSO_SHAPE.RIGHT_ARROW, | |
| "up_arrow": MSO_SHAPE.UP_ARROW, | |
| "down_arrow": MSO_SHAPE.DOWN_ARROW, | |
| "left_arrow": MSO_SHAPE.LEFT_ARROW, | |
| "curved_arrow": MSO_SHAPE.CURVED_RIGHT_ARROW, | |
| "curved_up_arrow": MSO_SHAPE.CURVED_UP_ARROW, | |
| "curved_down_arrow": MSO_SHAPE.CURVED_DOWN_ARROW, | |
| "curved_left_arrow": MSO_SHAPE.CURVED_LEFT_ARROW, | |
| "smiley_face": MSO_SHAPE.SMILEY_FACE, | |
| "heart": MSO_SHAPE.HEART, | |
| "lightning_bolt": MSO_SHAPE.LIGHTNING_BOLT, | |
| "sun": MSO_SHAPE.SUN, | |
| "moon": MSO_SHAPE.MOON, | |
| "cloud": MSO_SHAPE.CLOUD, | |
| "arc": MSO_SHAPE.ARC, | |
| "bracket": MSO_SHAPE.LEFT_BRACKET, | |
| "brace": MSO_SHAPE.LEFT_BRACE, | |
| "can": MSO_SHAPE.CAN, | |
| "cube": MSO_SHAPE.CUBE, | |
| "gear": MSO_SHAPE.GEAR_6, | |
| "donut": MSO_SHAPE.DONUT, | |
| "chart": MSO_SHAPE.PIE, | |
| "plus": MSO_SHAPE.MATH_PLUS, | |
| "minus": MSO_SHAPE.MATH_MINUS, | |
| "multiply": MSO_SHAPE.MATH_MULTIPLY, | |
| "divide": MSO_SHAPE.MATH_DIVIDE, | |
| "equal": MSO_SHAPE.MATH_EQUAL, | |
| "not_equal": MSO_SHAPE.MATH_NOT_EQUAL | |
| } | |
| # Stock icon categories with predefined colors | |
| ICON_CATEGORIES = { | |
| "business": ["chart", "gear", "cube", "can", "plus", "minus", "equal"], | |
| "arrows": ["arrow", "up_arrow", "down_arrow", "left_arrow", "curved_arrow"], | |
| "basic": ["rectangle", "oval", "triangle", "diamond", "star"], | |
| "nature": ["sun", "moon", "cloud", "lightning_bolt"], | |
| "emotion": ["smiley_face", "heart"], | |
| "math": ["plus", "minus", "multiply", "divide", "equal", "not_equal"] | |
| } | |
| # Local image library path | |
| IMAGE_LIBRARY_PATH = "images" | |
| os.makedirs(IMAGE_LIBRARY_PATH, exist_ok=True) | |
| # Pexels API for stock images (need to get a free API key) | |
| PEXELS_API_KEY = os.getenv("PEXELS_API_KEY", "") | |
| def search_stock_images(query, per_page=10): | |
| """Search for stock images using Pexels API""" | |
| if not PEXELS_API_KEY: | |
| st.warning("Pexels API key not set. Using placeholder images.") | |
| return [{"url": f"https://via.placeholder.com/800x600?text=Placeholder+{i}", | |
| "thumbnail": f"https://via.placeholder.com/100x100?text=Thumbnail+{i}"} | |
| for i in range(1, 6)] | |
| try: | |
| headers = {"Authorization": PEXELS_API_KEY} | |
| response = requests.get( | |
| f"https://api.pexels.com/v1/search?query={query}&per_page={per_page}", | |
| headers=headers | |
| ) | |
| if response.status_code == 200: | |
| data = response.json() | |
| return [{"url": photo["src"]["large"], | |
| "thumbnail": photo["src"]["tiny"]} | |
| for photo in data.get("photos", [])] | |
| else: | |
| st.error(f"Error fetching stock images: {response.status_code}") | |
| return [] | |
| except Exception as e: | |
| st.error(f"Error in stock image search: {str(e)}") | |
| return [] | |
| def download_image(url, filename=None): | |
| """Download image from URL and save to local library""" | |
| try: | |
| response = requests.get(url) | |
| if response.status_code == 200: | |
| image_data = BytesIO(response.content) | |
| if filename: | |
| filepath = os.path.join(IMAGE_LIBRARY_PATH, filename) | |
| with open(filepath, 'wb') as f: | |
| f.write(response.content) | |
| return filepath | |
| else: | |
| return image_data | |
| else: | |
| st.error(f"Error downloading image: {response.status_code}") | |
| return None | |
| except Exception as e: | |
| st.error(f"Error downloading image: {str(e)}") | |
| return None | |
| def get_icon_suggestions(slide_content): | |
| """Get icon suggestions based on slide content""" | |
| content_text = "" | |
| if isinstance(slide_content.get('content', []), list): | |
| content_text = " ".join(slide_content.get('content', [])) | |
| else: | |
| content_text = str(slide_content.get('content', '')) | |
| keywords = { | |
| "growth": ["up_arrow", "chart"], | |
| "decline": ["down_arrow"], | |
| "innovation": ["gear", "lightning_bolt"], | |
| "success": ["star", "smiley_face"], | |
| "challenge": ["triangle"], | |
| "balance": ["equal"], | |
| "addition": ["plus"], | |
| "subtraction": ["minus"], | |
| "division": ["divide"], | |
| "partnership": ["heart"], | |
| "business": ["cube", "can"], | |
| "nature": ["sun", "cloud"], | |
| "direction": ["arrow", "curved_arrow"] | |
| } | |
| suggested_icons = [] | |
| for keyword, icons in keywords.items(): | |
| if keyword.lower() in content_text.lower(): | |
| suggested_icons.extend(icons) | |
| # If no specific matches, return some general business icons | |
| if not suggested_icons: | |
| suggested_icons = random.sample(ICON_CATEGORIES["business"], min(3, len(ICON_CATEGORIES["business"]))) | |
| # Deduplicate | |
| return list(set(suggested_icons)) | |
| def get_shape_by_name(shape_name): | |
| """Get shape enum from name""" | |
| return STANDARD_SHAPES.get(shape_name.lower(), MSO_SHAPE.RECTANGLE) | |
| def add_shape_to_slide(slide, shape_name, left=Inches(1), top=Inches(1), width=Inches(1), height=Inches(1), fill_color=None): | |
| """Add a shape to a slide""" | |
| shape_type = get_shape_by_name(shape_name) | |
| shape = slide.shapes.add_shape(shape_type, left, top, width, height) | |
| if fill_color: | |
| # Convert hex color to RGB | |
| if fill_color.startswith('#'): | |
| r = int(fill_color[1:3], 16) | |
| g = int(fill_color[3:5], 16) | |
| b = int(fill_color[5:7], 16) | |
| shape.fill.solid() | |
| shape.fill.fore_color.rgb = RGBColor(r, g, b) | |
| return shape | |
| def add_image_to_slide(slide, image_stream, left=Inches(1), top=Inches(1), width=Inches(4), height=Inches(3)): | |
| """Add an image to a slide""" | |
| try: | |
| picture = slide.shapes.add_picture(image_stream, left, top, width, height) | |
| return picture | |
| except Exception as e: | |
| st.error(f"Error adding image to slide: {str(e)}") | |
| return None | |
| def create_chart_based_on_content(slide, chart_type, data, left=Inches(1), top=Inches(1), width=Inches(6), height=Inches(4)): | |
| """Create a chart based on the slide content""" | |
| try: | |
| # Basic chart types supported by python-pptx | |
| chart_types = { | |
| "bar": 1, # MSO_CHART_TYPE.BAR_CLUSTERED | |
| "column": 0, # MSO_CHART_TYPE.COLUMN_CLUSTERED | |
| "line": 4, # MSO_CHART_TYPE.LINE | |
| "pie": 5, # MSO_CHART_TYPE.PIE | |
| "scatter": 15, # MSO_CHART_TYPE.SCATTER | |
| "area": 8, # MSO_CHART_TYPE.AREA | |
| } | |
| chart_type_id = chart_types.get(chart_type.lower(), 0) # Default to column chart | |
| # Create sample data if not provided | |
| if not data: | |
| data = { | |
| "categories": ["Category 1", "Category 2", "Category 3", "Category 4"], | |
| "series": [ | |
| { | |
| "name": "Series 1", | |
| "values": [4.3, 2.5, 3.5, 4.5] | |
| }, | |
| { | |
| "name": "Series 2", | |
| "values": [2.4, 4.4, 1.8, 2.8] | |
| } | |
| ] | |
| } | |
| # Add chart to slide | |
| chart = slide.shapes.add_chart( | |
| chart_type_id, | |
| left, top, width, height | |
| ).chart | |
| # Set chart data | |
| chart_data = chart.chart_data | |
| # Set categories | |
| chart_data.categories = data.get("categories", ["Category 1", "Category 2", "Category 3"]) | |
| # Add series | |
| for i, series in enumerate(data.get("series", [])): | |
| if i == 0: | |
| # First series already exists | |
| chart_data.series[0].name = series.get("name", f"Series {i+1}") | |
| chart_data.series[0].values = series.get("values", [1, 2, 3]) | |
| else: | |
| # Add additional series | |
| new_series = chart_data.series.add() | |
| new_series.name = series.get("name", f"Series {i+1}") | |
| new_series.values = series.get("values", [1, 2, 3]) | |
| return chart | |
| except Exception as e: | |
| st.error(f"Error creating chart: {str(e)}") | |
| return None | |
| def analyze_slide_for_visuals(slide_content): | |
| """Analyze slide content to suggest appropriate visuals""" | |
| content_text = "" | |
| if isinstance(slide_content.get('content', []), list): | |
| content_text = " ".join(slide_content.get('content', [])) | |
| else: | |
| content_text = str(slide_content.get('content', '')) | |
| title_text = slide_content.get('title', '') | |
| purpose = slide_content.get('purpose', '') | |
| # Analyze for data visualization needs | |
| data_viz_keywords = [ | |
| 'compare', 'comparison', 'versus', 'vs', | |
| 'trend', 'growth', 'decline', 'increase', 'decrease', | |
| 'percentage', 'proportion', 'distribution', | |
| 'data', 'statistics', 'numbers', 'metrics', 'kpi' | |
| ] | |
| # Chart type suggestions based on content | |
| chart_suggestions = { | |
| 'comparison': 'bar', | |
| 'trend': 'line', | |
| 'growth': 'line', | |
| 'decline': 'line', | |
| 'percentage': 'pie', | |
| 'proportion': 'pie', | |
| 'distribution': 'column', | |
| 'correlation': 'scatter', | |
| } | |
| need_chart = False | |
| suggested_chart_type = 'column' | |
| for keyword in data_viz_keywords: | |
| if keyword.lower() in content_text.lower() or keyword.lower() in title_text.lower(): | |
| need_chart = True | |
| # Find most appropriate chart type | |
| for chart_keyword, chart_type in chart_suggestions.items(): | |
| if chart_keyword.lower() in content_text.lower() or chart_keyword.lower() in title_text.lower(): | |
| suggested_chart_type = chart_type | |
| break | |
| # Analyze for image needs | |
| image_keywords = [ | |
| 'show', 'display', 'picture', 'image', 'photo', 'visual', | |
| 'illustration', 'diagram', 'screenshot', 'graphic' | |
| ] | |
| need_image = False | |
| image_search_query = "" | |
| for keyword in image_keywords: | |
| if keyword.lower() in content_text.lower() or keyword.lower() in purpose.lower(): | |
| need_image = True | |
| # Try to extract the subject of the image | |
| text_parts = content_text.split() | |
| keyword_index = -1 | |
| for i, part in enumerate(text_parts): | |
| if keyword.lower() in part.lower(): | |
| keyword_index = i | |
| break | |
| if keyword_index != -1 and keyword_index < len(text_parts) - 1: | |
| # Take the next few words as the image subject | |
| image_search_query = " ".join(text_parts[keyword_index+1:keyword_index+4]) | |
| else: | |
| # Use the title as a fallback | |
| image_search_query = title_text | |
| # If no specific image query was found, use title | |
| if not image_search_query: | |
| image_search_query = title_text | |
| # Analyze for icon needs | |
| icon_suggestions = get_icon_suggestions(slide_content) | |
| # Analyze layout needs | |
| layout_keywords = { | |
| 'compare': 'Two Column', | |
| 'versus': 'Two Column', | |
| 'vs': 'Two Column', | |
| 'quote': 'Quote', | |
| 'testimonial': 'Quote', | |
| 'picture': 'Picture with Caption', | |
| 'image': 'Picture with Caption', | |
| 'photo': 'Picture with Caption', | |
| 'overview': 'Title Only', | |
| 'list': 'Standard' | |
| } | |
| suggested_layout = 'Standard' | |
| for keyword, layout in layout_keywords.items(): | |
| if (keyword.lower() in content_text.lower() or | |
| keyword.lower() in title_text.lower() or | |
| keyword.lower() in purpose.lower()): | |
| suggested_layout = layout | |
| break | |
| return { | |
| 'need_chart': need_chart, | |
| 'chart_type': suggested_chart_type, | |
| 'need_image': need_image, | |
| 'image_query': image_search_query, | |
| 'icon_suggestions': icon_suggestions, | |
| 'suggested_layout': suggested_layout | |
| } | |
| def get_random_placeholder_image(): | |
| """Generate a random placeholder image""" | |
| width = random.randint(800, 1200) | |
| height = random.randint(600, 800) | |
| image_url = f"https://via.placeholder.com/{width}x{height}?text=SlideGator+Placeholder" | |
| return download_image(image_url) | |
| def get_default_icon_set(): | |
| """Return a set of default icons for common topics""" | |
| return { | |
| "business": ["cube", "gear", "chart"], | |
| "growth": ["up_arrow", "chart"], | |
| "teamwork": ["heart", "smiley_face"], | |
| "innovation": ["lightning_bolt", "star"], | |
| "nature": ["sun", "cloud"], | |
| "time": ["clock", "hourglass"] | |
| } | |
| def generate_html_preview_with_visuals(slide, template_name): | |
| """Generate HTML preview that includes visual representations of charts, icons, images""" | |
| from utils import TEMPLATES | |
| # Get template info | |
| template = TEMPLATES.get(template_name, TEMPLATES["professional"]) | |
| colors = template["colors"] | |
| fonts = template["fonts"] | |
| # Prepare content | |
| title = slide.get('title', 'Untitled Slide') | |
| if isinstance(slide.get('content', []), list): | |
| content = slide.get('content', []) | |
| else: | |
| content_text = slide.get('content', '') | |
| content = content_text.split('\n') if content_text else [] | |
| # Determine layout based on slide design or content | |
| layout_type = "standard" | |
| if "design" in slide and "layout" in slide["design"]: | |
| layout_type = slide["design"]["layout"].lower() | |
| # Check for visual elements | |
| visual_elements = slide.get('visual_elements', []) | |
| if isinstance(visual_elements, str): | |
| visual_elements = [visual_elements] | |
| # Analyze what visuals would be appropriate | |
| visual_analysis = analyze_slide_for_visuals(slide) | |
| # Prepare visual HTML | |
| visual_html = "" | |
| # Handle charts if needed | |
| if visual_analysis['need_chart'] or any('chart' in str(ve).lower() for ve in visual_elements): | |
| chart_type = visual_analysis['chart_type'] | |
| chart_html = f""" | |
| <div class="chart-container" style="height: 150px; margin-bottom: 15px; border: 1px dashed {colors['accent']}; text-align: center; line-height: 150px;"> | |
| <div style="display: inline-block; vertical-align: middle;"> | |
| <div style="font-weight: bold;">{chart_type.capitalize()} Chart</div> | |
| <div style="font-size: 12px; color: {colors['accent']};">Based on slide content analysis</div> | |
| </div> | |
| </div> | |
| """ | |
| visual_html += chart_html | |
| # Handle images if needed | |
| if visual_analysis['need_image'] or any('image' in str(ve).lower() for ve in visual_elements): | |
| image_query = visual_analysis['image_query'] | |
| image_html = f""" | |
| <div class="image-container" style="height: 150px; margin-bottom: 15px; border: 1px dashed {colors['accent']}; text-align: center; line-height: 150px;"> | |
| <div style="display: inline-block; vertical-align: middle;"> | |
| <div style="font-weight: bold;">Image: "{image_query}"</div> | |
| <div style="font-size: 12px; color: {colors['accent']};">Click 'Add Image' to select</div> | |
| </div> | |
| </div> | |
| """ | |
| visual_html += image_html | |
| # Handle icons if needed | |
| if visual_analysis['icon_suggestions']: | |
| icons = visual_analysis['icon_suggestions'] | |
| icon_html = f""" | |
| <div class="icon-container" style="margin-bottom: 15px; text-align: center;"> | |
| <div style="display: inline-block; margin: 0 5px; font-size: 24px;">{"⚙️"}</div> | |
| <div style="display: inline-block; margin: 0 5px; font-size: 24px;">{"📈"}</div> | |
| <div style="display: inline-block; margin: 0 5px; font-size: 24px;">{"🔍"}</div> | |
| </div> | |
| """ | |
| visual_html += icon_html | |
| # Generate HTML based on layout type | |
| if "two column" in layout_type or "comparison" in layout_type: | |
| # Two-column layout | |
| mid_point = len(content) // 2 | |
| left_content = content[:mid_point] | |
| right_content = content[mid_point:] | |
| content_html = f""" | |
| <div class="row" style="display: flex; flex-direction: row;"> | |
| <div class="col" style="flex: 1; padding-right: 10px;"> | |
| <ul> | |
| {"".join(f'<li style="margin-bottom: 8px;">{item}</li>' for item in left_content)} | |
| </ul> | |
| </div> | |
| <div class="col" style="flex: 1; padding-left: 10px;"> | |
| <ul> | |
| {"".join(f'<li style="margin-bottom: 8px;">{item}</li>' for item in right_content)} | |
| </ul> | |
| </div> | |
| </div> | |
| """ | |
| elif "quote" in layout_type: | |
| # Quote layout | |
| if content: | |
| quote_text = content[0] if isinstance(content, list) and len(content) > 0 else "Quote text" | |
| author = content[1] if isinstance(content, list) and len(content) > 1 else "" | |
| content_html = f""" | |
| <div class="quote-container" style="text-align: center; padding: 20px;"> | |
| <blockquote style="font-size: 24px; font-style: italic; color: {colors['accent']};"> | |
| "{quote_text}" | |
| </blockquote> | |
| {f'<div style="text-align: right; font-size: 18px;">— {author}</div>' if author else ''} | |
| </div> | |
| """ | |
| else: | |
| content_html = '<div class="quote-container" style="text-align: center;"><p>No quote content</p></div>' | |
| elif "picture" in layout_type: | |
| # Picture with caption layout | |
| caption = content[0] if content else "Image caption" | |
| content_html = f""" | |
| <div class="picture-container" style="text-align: center;"> | |
| <div class="image-placeholder" style="height: 150px; border: 1px dashed {colors['accent']}; margin-bottom: 10px; line-height: 150px;"> | |
| <span>Image Area</span> | |
| </div> | |
| <div class="caption" style="font-style: italic; color: {colors['text']};">{caption}</div> | |
| </div> | |
| """ | |
| else: | |
| # Standard layout with bullet points | |
| content_html = f""" | |
| <ul> | |
| {"".join(f'<li style="margin-bottom: 8px;">{item}</li>' for item in content)} | |
| </ul> | |
| """ | |
| # Create the full HTML preview | |
| html = f""" | |
| <div class="slide-preview" style="background-color: {colors['secondary']}; border: 1px solid #dee2e6; border-radius: 4px; padding: 20px; height: 300px; overflow: auto; font-family: {fonts['body']};"> | |
| <div class="slide-title" style="color: {colors['text']}; font-family: {fonts['title']}; font-size: 24px; margin-bottom: 20px; border-bottom: 2px solid {colors['primary']}; padding-bottom: 10px;"> | |
| {title} | |
| </div> | |
| <div class="slide-visuals"> | |
| {visual_html} | |
| </div> | |
| <div class="slide-content" style="color: {colors['text']}; font-family: {fonts['body']}; font-size: 16px;"> | |
| {content_html} | |
| </div> | |
| </div> | |
| """ | |
| return html | |
| def apply_visuals_to_pptx_slide(slide, slide_content, template_colors): | |
| """Apply the suggested visuals to an actual PowerPoint slide""" | |
| # Get content areas | |
| content_placeholders = [shape for shape in slide.placeholders | |
| if shape.placeholder_format.type != 1] # 1 is title | |
| # No content placeholders, can't continue | |
| if not content_placeholders: | |
| return slide | |
| # Analyze visual needs | |
| visual_analysis = analyze_slide_for_visuals(slide_content) | |
| visual_elements = slide_content.get('visual_elements', []) | |
| # Handle charts | |
| if visual_analysis['need_chart'] or any('chart' in str(ve).lower() for ve in visual_elements): | |
| # Getting a reference to the content placeholder | |
| placeholder = content_placeholders[0] | |
| # Extract placeholder dimensions | |
| left = placeholder.left | |
| top = placeholder.top | |
| width = placeholder.width | |
| height = placeholder.height / 2 # Use half the height for the chart | |
| # Sample chart data | |
| data = { | |
| "categories": ["Q1", "Q2", "Q3", "Q4"], | |
| "series": [ | |
| { | |
| "name": "Series 1", | |
| "values": [4.3, 2.5, 3.5, 4.5] | |
| }, | |
| { | |
| "name": "Series 2", | |
| "values": [2.4, 4.4, 1.8, 2.8] | |
| } | |
| ] | |
| } | |
| create_chart_based_on_content(slide, visual_analysis['chart_type'], data, left, top, width, height) | |
| # Adjust content placeholder position to make room for chart | |
| placeholder.top = top + height + Inches(0.5) | |
| placeholder.height = height | |
| # Handle icons if requested | |
| if visual_analysis['icon_suggestions']: | |
| icons = visual_analysis['icon_suggestions'][:3] # Limit to 3 icons | |
| # Get last shape for reference | |
| if len(slide.shapes) > 0: | |
| last_shape = slide.shapes[-1] | |
| left = Inches(0.5) | |
| top = last_shape.top + last_shape.height + Inches(0.2) | |
| else: | |
| left = Inches(0.5) | |
| top = Inches(3) | |
| # Add icons | |
| for i, icon_name in enumerate(icons): | |
| if icon_name in STANDARD_SHAPES: | |
| icon_left = left + (i * Inches(1.2)) | |
| add_shape_to_slide( | |
| slide, | |
| icon_name, | |
| left=icon_left, | |
| top=top, | |
| width=Inches(0.8), | |
| height=Inches(0.8), | |
| fill_color=template_colors.get('accent', '#0000FF') | |
| ) | |
| return slide | |
| def get_ai_generated_image_placeholder(): | |
| """Create a base64 encoded placeholder for AI-generated images""" | |
| # Simple placeholder SVG | |
| svg = ''' | |
| <svg width="400" height="300" xmlns="http://www.w3.org/2000/svg"> | |
| <rect width="100%" height="100%" fill="#f0f0f0"/> | |
| <text x="50%" y="50%" font-family="Arial" font-size="20" fill="#666" text-anchor="middle"> | |
| AI Generated Image | |
| </text> | |
| </svg> | |
| ''' | |
| encoded = base64.b64encode(svg.encode('utf-8')).decode('utf-8') | |
| return f"data:image/svg+xml;base64,{encoded}" | |
| def generate_image_prompt(slide_content): | |
| """Generate a prompt for AI image generation based on slide content""" | |
| title = slide_content.get('title', '') | |
| if isinstance(slide_content.get('content', []), list): | |
| content_text = " ".join(slide_content.get('content', [])) | |
| else: | |
| content_text = str(slide_content.get('content', '')) | |
| prompt = f"Create an image for a slide titled '{title}' with content about {content_text[:100]}..." | |
| return prompt |