import logging import base64 import io from typing import Dict, Any, Optional, Tuple, List from .rendererBaseTemplate import BaseRenderer logger = logging.getLogger(__name__) class RendererPptx(BaseRenderer): """Renderer for PowerPoint (.pptx) files using python-pptx library.""" def __init__(self): super().__init__() self.supported_formats = ["pptx", "ppt"] self.output_mime_type = "application/vnd.openxmlformats-officedocument.presentationml.presentation" @classmethod def get_supported_formats(cls) -> list: """Get list of supported output formats.""" return ["pptx", "ppt"] async def render(self, extracted_content: Dict[str, Any], title: str, user_prompt: str = None, ai_service=None) -> Tuple[str, str]: """ Render content as PowerPoint presentation from JSON data. Args: extracted_content: JSON content to render as presentation title: Title for the presentation user_prompt: User prompt for AI styling ai_service: AI service for styling **kwargs: Additional rendering options Returns: Base64-encoded PowerPoint presentation as string """ try: # Import python-pptx from pptx import Presentation from pptx.util import Inches, Pt from pptx.enum.text import PP_ALIGN from pptx.dml.color import RGBColor import re # Get AI-generated styling definitions first styles = await self._get_pptx_styles(user_prompt, ai_service) # Create new presentation prs = Presentation() # Set slide size based on user intent (default to 16:9) slide_size = styles.get("slide_size", "16:9") if slide_size == "4:3": prs.slide_width = Inches(10) prs.slide_height = Inches(7.5) else: # Default to 16:9 prs.slide_width = Inches(13.33) prs.slide_height = Inches(7.5) # Generate slides from JSON content slides_data = await self._parse_json_to_slides(extracted_content, title, styles) logger.info(f"Parsed {len(slides_data)} slides from JSON content") # Debug: Show first 200 chars of content logger.info(f"JSON content preview: {str(extracted_content)[:200]}...") for i, slide_data in enumerate(slides_data): logger.info(f"Slide {i+1}: '{slide_data.get('title', 'No title')}' - {len(slide_data.get('content', ''))} chars") # Debug: Show slide content preview slide_content = slide_data.get('content', '') if slide_content: logger.info(f" Content preview: '{slide_content[:100]}...'") else: logger.warning(f" ⚠️ Slide {i+1} has NO content!") # Create slide with appropriate layout based on content slide_layout_index = self._get_slide_layout_index(slide_data, styles) slide_layout = prs.slide_layouts[slide_layout_index] slide = prs.slides.add_slide(slide_layout) # Set title with AI-generated styling title_shape = slide.shapes.title title_shape.text = slide_data.get("title", "Slide") # Apply title styling title_style = styles.get("title", {}) if title_shape.text_frame.paragraphs[0].font: title_shape.text_frame.paragraphs[0].font.size = Pt(title_style.get("font_size", 44)) title_shape.text_frame.paragraphs[0].font.bold = title_style.get("bold", True) title_color = self._get_safe_color(title_style.get("color", (31, 78, 121))) title_shape.text_frame.paragraphs[0].font.color.rgb = RGBColor(*title_color) # Set content with AI-generated styling content_shape = slide.placeholders[1] content_text = slide_data.get("content", "") # Format content text with AI styles text_frame = content_shape.text_frame text_frame.clear() # Split content into paragraphs paragraphs = content_text.split('\n\n') for i, paragraph in enumerate(paragraphs): if paragraph.strip(): if i == 0: p = text_frame.paragraphs[0] else: p = text_frame.add_paragraph() p.text = paragraph.strip() # Apply AI-generated styling based on content type if paragraph.startswith('#'): # Header p.text = paragraph.lstrip('#').strip() heading_style = styles.get("heading", {}) p.font.size = Pt(heading_style.get("font_size", 32)) p.font.bold = heading_style.get("bold", True) heading_color = self._get_safe_color(heading_style.get("color", (47, 47, 47))) p.font.color.rgb = RGBColor(*heading_color) elif paragraph.startswith('##'): # Subheader p.text = paragraph.lstrip('#').strip() subheading_style = styles.get("subheading", {}) p.font.size = Pt(subheading_style.get("font_size", 24)) p.font.bold = subheading_style.get("bold", True) subheading_color = self._get_safe_color(subheading_style.get("color", (79, 79, 79))) p.font.color.rgb = RGBColor(*subheading_color) elif paragraph.startswith('*') and paragraph.endswith('*'): # Bold text p.text = paragraph.strip('*') paragraph_style = styles.get("paragraph", {}) p.font.size = Pt(paragraph_style.get("font_size", 18)) p.font.bold = True paragraph_color = self._get_safe_color(paragraph_style.get("color", (47, 47, 47))) p.font.color.rgb = RGBColor(*paragraph_color) else: # Regular text paragraph_style = styles.get("paragraph", {}) p.font.size = Pt(paragraph_style.get("font_size", 18)) p.font.bold = paragraph_style.get("bold", False) paragraph_color = self._get_safe_color(paragraph_style.get("color", (47, 47, 47))) p.font.color.rgb = RGBColor(*paragraph_color) # Apply alignment align = paragraph_style.get("align", "left") if align == "center": p.alignment = PP_ALIGN.CENTER elif align == "right": p.alignment = PP_ALIGN.RIGHT else: p.alignment = PP_ALIGN.LEFT # If no slides were created, create a default slide if not slides_data: slide_layout = prs.slide_layouts[0] # Title slide layout slide = prs.slides.add_slide(slide_layout) title_shape = slide.shapes.title title_shape.text = title # Apply title styling to default slide title_style = styles.get("title", {}) if title_shape.text_frame.paragraphs[0].font: title_shape.text_frame.paragraphs[0].font.size = Pt(title_style.get("font_size", 48)) title_shape.text_frame.paragraphs[0].font.bold = title_style.get("bold", True) title_color = self._get_safe_color(title_style.get("color", (31, 78, 121))) title_shape.text_frame.paragraphs[0].font.color.rgb = RGBColor(*title_color) subtitle_shape = slide.placeholders[1] subtitle_shape.text = "Generated by PowerOn AI System" # Apply subtitle styling paragraph_style = styles.get("paragraph", {}) if subtitle_shape.text_frame.paragraphs[0].font: subtitle_shape.text_frame.paragraphs[0].font.size = Pt(paragraph_style.get("font_size", 20)) subtitle_shape.text_frame.paragraphs[0].font.bold = paragraph_style.get("bold", False) paragraph_color = self._get_safe_color(paragraph_style.get("color", (47, 47, 47))) subtitle_shape.text_frame.paragraphs[0].font.color.rgb = RGBColor(*paragraph_color) # Save to buffer buffer = io.BytesIO() prs.save(buffer) buffer.seek(0) # Convert to base64 pptx_bytes = buffer.getvalue() pptx_base64 = base64.b64encode(pptx_bytes).decode('utf-8') logger.info(f"Successfully rendered PowerPoint presentation: {len(pptx_bytes)} bytes") return pptx_base64, "application/vnd.openxmlformats-officedocument.presentationml.presentation" except ImportError: logger.error("python-pptx library not installed. Install with: pip install python-pptx") return "python-pptx library not installed", "text/plain" except Exception as e: logger.error(f"Error rendering PowerPoint presentation: {str(e)}") return f"Error rendering PowerPoint presentation: {str(e)}", "text/plain" def _parse_content_to_slides(self, content: str, title: str) -> list: """ Parse content into slide data structure. Args: content: Content to parse title: Presentation title Returns: List of slide data dictionaries """ slides = [] # Split content by slide markers or headers slide_sections = self._split_content_into_slides(content) for i, section in enumerate(slide_sections): if section.strip(): slide_data = { "title": f"Slide {i + 1}", "content": section.strip() } # Extract title from content if it starts with # lines = section.strip().split('\n') if lines and lines[0].startswith('#'): # Remove # symbols and clean up title slide_title = lines[0].lstrip('#').strip() slide_data["title"] = slide_title slide_data["content"] = '\n'.join(lines[1:]).strip() elif lines and lines[0].strip(): # Use first line as title if it looks like a title first_line = lines[0].strip() if len(first_line) < 100 and not first_line.endswith('.'): slide_data["title"] = first_line slide_data["content"] = '\n'.join(lines[1:]).strip() slides.append(slide_data) return slides def _split_content_into_slides(self, content: str) -> list: """ Split content into individual slides based on headers and structure. Args: content: Content to split Returns: List of slide content strings """ import re # First, try to split by major headers (# or ##) # This is the most common case for AI-generated content header_pattern = r'^(#{1,2})\s+(.+)$' lines = content.split('\n') slides = [] current_slide = [] for line in lines: # Check if this line is a header header_match = re.match(header_pattern, line.strip()) if header_match: # If we have content in current slide, save it if current_slide: slide_content = '\n'.join(current_slide).strip() if slide_content: slides.append(slide_content) current_slide = [] # Start new slide with this header current_slide.append(line) else: # Add line to current slide current_slide.append(line) # Add the last slide if current_slide: slide_content = '\n'.join(current_slide).strip() if slide_content: slides.append(slide_content) # If we found slides with headers, return them if len(slides) > 1: return slides # Fallback: Split by double newlines sections = content.split('\n\n\n') if len(sections) > 1: return [s.strip() for s in sections if s.strip()] # Another fallback: Split by double newlines sections = content.split('\n\n') if len(sections) > 1: return [s.strip() for s in sections if s.strip()] # Last resort: return as single slide return [content.strip()] def get_output_mime_type(self) -> str: """Get MIME type for rendered output.""" return self.output_mime_type async def _get_pptx_styles(self, user_prompt: str, ai_service=None) -> Dict[str, Any]: """Get PowerPoint styling definitions using base template AI styling.""" style_schema = { "title": {"font_size": 52, "color": "#1B365D", "bold": True, "align": "center"}, "heading": {"font_size": 36, "color": "#2C5F2D", "bold": True, "align": "left"}, "subheading": {"font_size": 28, "color": "#4A90E2", "bold": True, "align": "left"}, "paragraph": {"font_size": 20, "color": "#2F2F2F", "bold": False, "align": "left"}, "bullet_list": {"font_size": 20, "color": "#2F2F2F", "indent": 20}, "table_header": {"font_size": 18, "color": "#FFFFFF", "bold": True, "background": "#1B365D"}, "table_cell": {"font_size": 16, "color": "#2F2F2F", "bold": False, "background": "#F8F9FA"}, "slide_size": "16:9", "content_per_slide": "concise", "design_theme": "corporate", "color_scheme": "professional", "background_style": "clean", "accent_colors": ["#1B365D", "#2C5F2D", "#4A90E2", "#6B7280"], "professional_grade": True, "executive_ready": True } style_template = self._create_professional_pptx_template(user_prompt, style_schema) # Use our own _get_ai_styles_with_pptx_colors method to ensure proper color conversion styles = await self._get_ai_styles_with_pptx_colors(ai_service, style_template, self._get_default_pptx_styles()) # Validate PowerPoint-specific requirements return self._validate_pptx_styles_readability(styles) def _create_professional_pptx_template(self, user_prompt: str, style_schema: Dict[str, Any]) -> str: """Create a professional PowerPoint-specific AI style template for corporate-quality slides.""" import json schema_json = json.dumps(style_schema, indent=4) return f"""Customize the JSON below for professional PowerPoint slides. User Request: {user_prompt or "Create professional corporate slides"} Rules: - Use professional colors (blues, grays, deep greens) - Large, readable font sizes - High contrast - Sophisticated color palettes Return ONLY this JSON with your changes: {schema_json} JSON ONLY. NO OTHER TEXT.""" async def _get_ai_styles_with_pptx_colors(self, ai_service, style_template: str, default_styles: Dict[str, Any]) -> Dict[str, Any]: """Get AI styles with proper PowerPoint color conversion.""" if not ai_service: return default_styles try: from modules.datamodels.datamodelAi import AiCallRequest, AiCallOptions, OperationType request_options = AiCallOptions() request_options.operationType = OperationType.GENERAL request = AiCallRequest(prompt=style_template, context="", options=request_options) # Check if AI service is properly configured if not hasattr(ai_service, 'aiObjects') or not ai_service.aiObjects: self.logger.warning("AI service not properly configured, using defaults") return default_styles response = await ai_service.aiObjects.call(request) # Check if response is valid if not response: self.logger.warning("AI service returned no response, using defaults") return default_styles import json import re # Clean and parse JSON result = response.content.strip() if response and response.content else "" # Check if result is empty if not result: self.logger.warning("AI styling returned empty response, using defaults") return default_styles # Log the raw response for debugging self.logger.debug(f"AI styling raw response: {result[:200]}...") # Extract JSON from various formats json_match = re.search(r'```json\s*\n(.*?)\n```', result, re.DOTALL) if json_match: result = json_match.group(1).strip() elif result.startswith('```json'): result = re.sub(r'^```json\s*', '', result) result = re.sub(r'\s*```$', '', result) elif result.startswith('```'): result = re.sub(r'^```\s*', '', result) result = re.sub(r'\s*```$', '', result) # Try to extract JSON from explanatory text json_patterns = [ r'\{[^{}]*"title"[^{}]*\}', # Simple JSON object r'\{.*?"title".*?\}', # JSON with title field r'\{.*?"font_size".*?\}', # JSON with font_size field ] for pattern in json_patterns: json_match = re.search(pattern, result, re.DOTALL) if json_match: result = json_match.group(0) break # Additional cleanup - remove any leading/trailing whitespace and newlines result = result.strip() # Check if result is still empty after cleanup if not result: self.logger.warning("AI styling returned empty content after cleanup, using defaults") return default_styles # Try to parse JSON try: styles = json.loads(result) self.logger.debug(f"Successfully parsed AI styles: {list(styles.keys())}") except json.JSONDecodeError as json_error: self.logger.warning(f"AI styling returned invalid JSON: {json_error}") self.logger.warning(f"Raw content that failed to parse: {result[:100]}...") # Try to extract just the JSON part if it's embedded in text json_start = result.find('{') json_end = result.rfind('}') if json_start != -1 and json_end != -1 and json_end > json_start: json_part = result[json_start:json_end+1] try: styles = json.loads(json_part) self.logger.info("Successfully extracted JSON from explanatory text") self.logger.debug(f"Extracted AI styles: {list(styles.keys())}") except json.JSONDecodeError: self.logger.warning("Could not extract valid JSON from response, using defaults") return default_styles else: return default_styles # Convert colors to PowerPoint RGB format styles = self._convert_colors_format(styles) return styles except Exception as e: self.logger.warning(f"AI styling failed: {str(e)}, using defaults") return default_styles def _convert_colors_format(self, styles: Dict[str, Any]) -> Dict[str, Any]: """Convert hex colors to RGB format for PowerPoint compatibility.""" try: for style_name, style_config in styles.items(): if isinstance(style_config, dict): for prop, value in style_config.items(): if isinstance(value, str) and value.startswith('#'): # Convert hex to RGB tuple for PowerPoint hex_color = value.lstrip('#') if len(hex_color) == 6: r = int(hex_color[0:2], 16) g = int(hex_color[2:4], 16) b = int(hex_color[4:6], 16) styles[style_name][prop] = (r, g, b) elif len(hex_color) == 8: # aRGB format r = int(hex_color[2:4], 16) g = int(hex_color[4:6], 16) b = int(hex_color[6:8], 16) styles[style_name][prop] = (r, g, b) return styles except Exception as e: self.logger.warning(f"Color conversion failed: {str(e)}") return styles def _get_safe_color(self, color_value, default=(0, 0, 0)) -> tuple: """Get a safe RGB color tuple for PowerPoint.""" if isinstance(color_value, tuple) and len(color_value) == 3: return color_value elif isinstance(color_value, str) and color_value.startswith('#'): hex_color = color_value.lstrip('#') if len(hex_color) == 6: r = int(hex_color[0:2], 16) g = int(hex_color[2:4], 16) b = int(hex_color[4:6], 16) return (r, g, b) elif len(hex_color) == 8: # aRGB format r = int(hex_color[2:4], 16) g = int(hex_color[4:6], 16) b = int(hex_color[6:8], 16) return (r, g, b) return default def _validate_pptx_styles_readability(self, styles: Dict[str, Any]) -> Dict[str, Any]: """Validate and fix readability issues in AI-generated styles.""" try: # Ensure minimum font sizes for PowerPoint readability min_font_sizes = { "title": 36, "heading": 24, "subheading": 20, "paragraph": 14, "bullet_list": 14, "table_header": 12, "table_cell": 12 } for style_name, min_size in min_font_sizes.items(): if style_name in styles: current_size = styles[style_name].get("font_size", 12) if current_size < min_size: styles[style_name]["font_size"] = min_size return styles except Exception as e: logger.warning(f"Style validation failed: {str(e)}") return self._get_default_pptx_styles() def _get_default_pptx_styles(self) -> Dict[str, Any]: """Default PowerPoint styles with corporate professional color scheme.""" return { "title": {"font_size": 52, "color": (27, 54, 93), "bold": True, "align": "center"}, "heading": {"font_size": 36, "color": (44, 95, 45), "bold": True, "align": "left"}, "subheading": {"font_size": 28, "color": (74, 144, 226), "bold": True, "align": "left"}, "paragraph": {"font_size": 20, "color": (47, 47, 47), "bold": False, "align": "left"}, "bullet_list": {"font_size": 20, "color": (47, 47, 47), "indent": 20}, "table_header": {"font_size": 18, "color": (255, 255, 255), "bold": True, "background": (27, 54, 93)}, "table_cell": {"font_size": 16, "color": (47, 47, 47), "bold": False, "background": (248, 249, 250)}, "slide_size": "16:9", "content_per_slide": "concise", "design_theme": "corporate", "color_scheme": "professional", "background_style": "clean", "accent_colors": [(27, 54, 93), (44, 95, 45), (74, 144, 226), (107, 114, 128)], "professional_grade": True, "executive_ready": True } async def _parse_json_to_slides(self, json_content: Dict[str, Any], title: str, styles: Dict[str, Any]) -> List[Dict[str, Any]]: """ Parse JSON content into slide data structure. Args: json_content: JSON content to parse title: Presentation title styles: AI-generated styles Returns: List of slide data dictionaries """ slides = [] try: # Validate JSON structure if not isinstance(json_content, dict): raise ValueError("JSON content must be a dictionary") if "sections" not in json_content: raise ValueError("JSON content must contain 'sections' field") # Use title from JSON metadata if available, otherwise use provided title document_title = json_content.get("metadata", {}).get("title", title) # Create title slide slides.append({ "title": document_title, "content": "Generated by PowerOn AI System\n\n" + self._format_timestamp() }) # Process sections into slides based on content and user intent sections = json_content.get("sections", []) slides.extend(self._create_slides_from_sections(sections, styles)) # If no content slides were created, create a default content slide if len(slides) == 1: # Only title slide slides.append({ "title": "Content Overview", "content": "No structured content found in the source documents.\n\nPlease check the source documents and try again." }) return slides except Exception as e: logger.error(f"Error parsing JSON to slides: {str(e)}") # Return minimal fallback slides return [ { "title": title, "content": "Error parsing content for presentation" } ] def _create_slide_from_section(self, section: Dict[str, Any], styles: Dict[str, Any]) -> Dict[str, Any]: """Create a slide from a JSON section.""" try: # Get section title from data or use default section_title = "Untitled Section" if section.get("content_type") == "heading": # Extract text from elements array for element in section.get("elements", []): if isinstance(element, dict) and "text" in element: section_title = element.get("text", "Untitled Section") break elif section.get("title"): section_title = section.get("title") content_type = section.get("content_type", "paragraph") elements = section.get("elements", []) # Build slide content based on section type content_parts = [] if content_type == "table": content_parts.append(self._format_table_for_slide(elements)) elif content_type == "list": content_parts.append(self._format_list_for_slide(elements)) elif content_type == "heading": content_parts.append(self._format_heading_for_slide(elements)) elif content_type == "paragraph": content_parts.append(self._format_paragraph_for_slide(elements)) elif content_type == "code": content_parts.append(self._format_code_for_slide(elements)) else: content_parts.append(self._format_paragraph_for_slide(elements)) # Combine content parts slide_content = "\n\n".join(filter(None, content_parts)) return { "title": section_title, "content": slide_content } except Exception as e: logger.warning(f"Error creating slide from section: {str(e)}") return None def _format_table_for_slide(self, elements: List[Dict[str, Any]]) -> str: """Format table data for slide presentation.""" try: # Extract table data from elements array headers = [] rows = [] for element in elements: if isinstance(element, dict) and "headers" in element and "rows" in element: headers = element.get("headers", []) rows = element.get("rows", []) break if not headers: return "" # Create table representation table_lines = [] # Add headers header_line = " | ".join(str(h) for h in headers) table_lines.append(header_line) # Add separator separator = "-" * len(header_line) table_lines.append(separator) # Add data rows (limit based on content density) max_rows = 5 # Default limit for row in rows[:max_rows]: row_line = " | ".join(str(cell) for cell in row) table_lines.append(row_line) if len(rows) > max_rows: table_lines.append(f"... and {len(rows) - max_rows} more rows") return "\n".join(table_lines) except Exception as e: logger.warning(f"Error formatting table for slide: {str(e)}") return "" def _format_list_for_slide(self, list_data: Dict[str, Any]) -> str: """Format list data for slide presentation.""" try: items = list_data.get("items", []) if not items: return "" # Create list representation list_lines = [] for item in items: if isinstance(item, dict): text = item.get("text", "") list_lines.append(f"• {text}") # Add subitems (limit to 3 for readability) subitems = item.get("subitems", [])[:3] for subitem in subitems: if isinstance(subitem, dict): list_lines.append(f" - {subitem.get('text', '')}") else: list_lines.append(f" - {subitem}") else: list_lines.append(f"• {str(item)}") return "\n".join(list_lines) except Exception as e: logger.warning(f"Error formatting list for slide: {str(e)}") return "" def _format_heading_for_slide(self, heading_data: Dict[str, Any]) -> str: """Format heading data for slide presentation.""" try: text = heading_data.get("text", "") level = heading_data.get("level", 1) if text: return f"{'#' * level} {text}" return "" except Exception as e: logger.warning(f"Error formatting heading for slide: {str(e)}") return "" def _format_paragraph_for_slide(self, paragraph_data: Dict[str, Any]) -> str: """Format paragraph data for slide presentation.""" try: text = paragraph_data.get("text", "") if text: # Limit paragraph length based on content density max_length = 200 # Default limit if len(text) > max_length: text = text[:max_length] + "..." return text return "" except Exception as e: logger.warning(f"Error formatting paragraph for slide: {str(e)}") return "" def _format_code_for_slide(self, code_data: Dict[str, Any]) -> str: """Format code data for slide presentation.""" try: code = code_data.get("code", "") language = code_data.get("language", "") if code: # Limit code length based on content density max_length = 100 # Default limit if len(code) > max_length: code = code[:max_length] + "..." if language: return f"Code ({language}):\n{code}" else: return f"Code:\n{code}" return "" except Exception as e: logger.warning(f"Error formatting code for slide: {str(e)}") return "" def _get_slide_layout_index(self, slide_data: Dict[str, Any], styles: Dict[str, Any]) -> int: """Determine the best professional slide layout based on content.""" try: content = slide_data.get("content", "") title = slide_data.get("title", "") # Check if it's a title slide (first slide) if not content or "Generated by PowerOn AI System" in content: return 0 # Title slide layout # Professional layout selection based on content if "|" in content and "-" in content: # Has both tables and lists - use content with caption for professional look return 2 elif "|" in content: # Has tables - use content layout for clean table presentation return 1 elif content.count("•") > 2: # Has many bullet points - use content layout for better readability return 1 elif len(content) > 200: # Long content - use content layout for better text flow return 1 elif title and len(title) > 20: # Long title - use title and content layout return 1 else: # Default to title and content layout for professional appearance return 1 except Exception as e: logger.warning(f"Error determining slide layout: {str(e)}") return 1 # Default to title and content layout def _create_slides_from_sections(self, sections: List[Dict[str, Any]], styles: Dict[str, Any]) -> List[Dict[str, Any]]: """Create slides from sections based on content density and user intent.""" try: slides = [] content_per_slide = styles.get("content_per_slide", "concise") # Group sections by type and create slides current_slide_content = [] current_slide_title = "Content Overview" for section in sections: section_type = section.get("content_type", "paragraph") elements = section.get("elements", []) if section_type == "heading": # If we have accumulated content, create a slide if current_slide_content: slides.append({ "title": current_slide_title, "content": "\n\n".join(current_slide_content) }) current_slide_content = [] # Start new slide with heading as title for element in elements: if isinstance(element, dict) and "text" in element: current_slide_title = element.get("text", "Untitled Section") break else: # Add content to current slide formatted_content = self._format_section_content(section) if formatted_content: current_slide_content.append(formatted_content) # Add final slide if there's content if current_slide_content: slides.append({ "title": current_slide_title, "content": "\n\n".join(current_slide_content) }) return slides except Exception as e: logger.warning(f"Error creating slides from sections: {str(e)}") return [] def _format_section_content(self, section: Dict[str, Any]) -> str: """Format section content for slide presentation.""" try: content_type = section.get("content_type", "paragraph") elements = section.get("elements", []) # Process each element in the section content_parts = [] for element in elements: if content_type == "table": content_parts.append(self._format_table_for_slide([element])) elif content_type == "list": content_parts.append(self._format_list_for_slide([element])) elif content_type == "heading": content_parts.append(self._format_heading_for_slide([element])) elif content_type == "paragraph": content_parts.append(self._format_paragraph_for_slide([element])) elif content_type == "code": content_parts.append(self._format_code_for_slide([element])) else: content_parts.append(self._format_paragraph_for_slide([element])) return "\n\n".join(filter(None, content_parts)) except Exception as e: logger.warning(f"Error formatting section content: {str(e)}") return "" def _format_timestamp(self) -> str: """Format current timestamp for presentation generation.""" from datetime import datetime, UTC return datetime.now(UTC).strftime("%Y-%m-%d %H:%M:%S UTC")