""" PowerPoint method module. Handles PowerPoint operations using the PowerPoint service. """ import logging from typing import Dict, Any, List, Optional from datetime import datetime, UTC import json import base64 from modules.workflow.methodBase import MethodBase, ActionResult, action logger = logging.getLogger(__name__) class PowerpointService: """Service for Microsoft PowerPoint operations using Graph API""" def __init__(self, serviceContainer: Any): self.serviceContainer = serviceContainer def _getMicrosoftConnection(self, connectionReference: str) -> Optional[Dict[str, Any]]: """Get Microsoft connection from connection reference""" try: userConnection = self.serviceContainer.getUserConnectionFromConnectionReference(connectionReference) if not userConnection or userConnection.authority != "msft" or userConnection.status != "active": return None # Get the corresponding token for this user and authority token = self.serviceContainer.interfaceApp.getToken(userConnection.authority) if not token: logger.warning(f"No token found for user {userConnection.userId} and authority {userConnection.authority}") return None return { "id": userConnection.id, "accessToken": token.tokenAccess, "refreshToken": token.tokenRefresh, "scopes": ["Mail.ReadWrite", "User.Read"] # Default Microsoft scopes } except Exception as e: logger.error(f"Error getting Microsoft connection: {str(e)}") return None async def readPresentation(self, fileId: str, connectionReference: str, includeSlides: bool = True) -> Dict[str, Any]: """Read PowerPoint presentation using Microsoft Graph API""" try: connection = self._getMicrosoftConnection(connectionReference) if not connection: return { "error": "No valid Microsoft connection found for the provided connection reference", "fileId": fileId, "connectionReference": connectionReference } # Get file data from service container file_data = self.serviceContainer.getFileData(fileId) file_info = self.serviceContainer.getFileInfo(fileId) if not file_data: return { "error": "File not found or empty", "fileId": fileId } # For now, simulate PowerPoint reading with AI analysis # In a real implementation, you would use Microsoft Graph API ppt_prompt = f""" Analyze this PowerPoint presentation and extract structured information. File: {file_info.get('name', 'Unknown')} Include slides: {includeSlides} File content (first 5000 characters): {file_data.decode('utf-8', errors='ignore')[:5000] if isinstance(file_data, bytes) else str(file_data)[:5000]} Please extract: 1. Presentation title and theme 2. Slide structure and content 3. Text content from each slide 4. Images and media references 5. Charts and data visualizations 6. Speaker notes if available 7. Overall presentation flow and messaging Return the data in a structured JSON format. """ # Use AI to analyze PowerPoint content analysis_result = await self.serviceContainer.interfaceAiCalls.callAiTextAdvanced(ppt_prompt) return { "fileId": fileId, "includeSlides": includeSlides, "data": analysis_result, "fileInfo": file_info, "connection": { "id": connection["id"], "authority": "microsoft", "reference": connectionReference } } except Exception as e: logger.error(f"Error reading presentation: {str(e)}") return { "error": str(e), "fileId": fileId } async def writePresentation(self, fileId: str, connectionReference: str, slides: List[Dict[str, Any]]) -> Dict[str, Any]: """Write to PowerPoint presentation using Microsoft Graph API""" try: connection = self._getMicrosoftConnection(connectionReference) if not connection: return { "error": "No valid Microsoft connection found for the provided connection reference", "fileId": fileId, "connectionReference": connectionReference } # For now, simulate PowerPoint writing # In a real implementation, you would use Microsoft Graph API write_prompt = f""" Prepare content for writing to PowerPoint presentation. File: {fileId} Number of slides: {len(slides)} Slides data: {json.dumps(slides, indent=2)} Please format this content appropriately for PowerPoint and provide: 1. Slide layouts and structures 2. Text content and formatting 3. Image and media placement 4. Chart and visualization specifications 5. Animation and transition suggestions """ # Use AI to prepare PowerPoint content prepared_content = await self.serviceContainer.interfaceAiCalls.callAiTextAdvanced(write_prompt) return { "fileId": fileId, "slides": slides, "content": prepared_content, "status": "prepared", "connection": { "id": connection["id"], "authority": "microsoft", "reference": connectionReference } } except Exception as e: logger.error(f"Error writing to presentation: {str(e)}") return { "error": str(e), "fileId": fileId } async def convertPresentation(self, fileId: str, connectionReference: str, format: str = "pdf") -> Dict[str, Any]: """Convert PowerPoint presentation to another format using Microsoft Graph API""" try: connection = self._getMicrosoftConnection(connectionReference) if not connection: return { "error": "No valid Microsoft connection found for the provided connection reference", "fileId": fileId, "connectionReference": connectionReference } # For now, simulate conversion # In a real implementation, you would use Microsoft Graph API convert_prompt = f""" Convert PowerPoint presentation to {format.upper()} format. File: {fileId} Target format: {format} Please provide: 1. Conversion specifications 2. Format-specific optimizations 3. Quality settings and options 4. Any special considerations for the target format """ # Use AI to describe conversion process conversion_result = await self.serviceContainer.interfaceAiCalls.callAiTextAdvanced(convert_prompt) # Create converted file using service container converted_file_id = self.serviceContainer.createFile( fileName=f"converted_presentation.{format}", mimeType=f"application/{format}", content=conversion_result, base64encoded=False ) return { "fileId": fileId, "format": format, "convertedFileId": converted_file_id, "result": conversion_result, "connection": { "id": connection["id"], "authority": "microsoft", "reference": connectionReference } } except Exception as e: logger.error(f"Error converting presentation: {str(e)}") return { "error": str(e), "fileId": fileId } async def createPresentation(self, fileName: str, connectionReference: str, template: str = None) -> Dict[str, Any]: """Create new PowerPoint presentation using Microsoft Graph API""" try: connection = self._getMicrosoftConnection(connectionReference) if not connection: return { "error": "No valid Microsoft connection found for the provided connection reference", "connectionReference": connectionReference } # For now, simulate presentation creation # In a real implementation, you would use Microsoft Graph API create_prompt = f""" Create a new PowerPoint presentation structure. File name: {fileName} Template: {template or 'Standard'} Please provide: 1. Initial slide structure 2. Default slide layouts 3. Theme and design elements 4. Sample content if template specified 5. Presentation guidelines """ # Use AI to create PowerPoint structure presentation_structure = await self.serviceContainer.interfaceAiCalls.callAiTextAdvanced(create_prompt) # Create file using service container file_id = self.serviceContainer.createFile( fileName=fileName, mimeType="application/vnd.openxmlformats-officedocument.presentationml.presentation", content=presentation_structure, base64encoded=False ) return { "fileId": file_id, "fileName": fileName, "template": template, "structure": presentation_structure, "connection": { "id": connection["id"], "authority": "microsoft", "reference": connectionReference } } except Exception as e: logger.error(f"Error creating presentation: {str(e)}") return { "error": str(e) } async def addSlide(self, fileId: str, connectionReference: str, layout: str = "title", content: Dict[str, Any] = None) -> Dict[str, Any]: """Add slide to presentation using Microsoft Graph API""" try: connection = self._getMicrosoftConnection(connectionReference) if not connection: return { "error": "No valid Microsoft connection found for the provided connection reference", "fileId": fileId, "connectionReference": connectionReference } # For now, simulate slide addition # In a real implementation, you would use Microsoft Graph API slide_prompt = f""" Add a new slide to PowerPoint presentation. File: {fileId} Layout: {layout} Content: {json.dumps(content, indent=2) if content else 'Default content'} Please provide: 1. Slide structure and layout 2. Content placement and formatting 3. Visual elements and design 4. Slide number and positioning """ # Use AI to create slide content slide_content = await self.serviceContainer.interfaceAiCalls.callAiTextAdvanced(slide_prompt) return { "fileId": fileId, "layout": layout, "content": content, "slideContent": slide_content, "connection": { "id": connection["id"], "authority": "microsoft", "reference": connectionReference } } except Exception as e: logger.error(f"Error adding slide: {str(e)}") return { "error": str(e), "fileId": fileId } async def addContent(self, fileId: str, connectionReference: str, slideId: str, content: Dict[str, Any]) -> Dict[str, Any]: """Add content to slide using Microsoft Graph API""" try: connection = self._getMicrosoftConnection(connectionReference) if not connection: return { "error": "No valid Microsoft connection found for the provided connection reference", "fileId": fileId, "connectionReference": connectionReference } # For now, simulate content addition # In a real implementation, you would use Microsoft Graph API content_prompt = f""" Add content to PowerPoint slide. File: {fileId} Slide ID: {slideId} Content: {json.dumps(content, indent=2)} Please provide: 1. Content placement and formatting 2. Text styling and layout 3. Image and media integration 4. Chart and visualization setup 5. Animation and effects """ # Use AI to format slide content formatted_content = await self.serviceContainer.interfaceAiCalls.callAiTextAdvanced(content_prompt) return { "fileId": fileId, "slideId": slideId, "content": content, "formattedContent": formatted_content, "connection": { "id": connection["id"], "authority": "microsoft", "reference": connectionReference } } except Exception as e: logger.error(f"Error adding content: {str(e)}") return { "error": str(e), "fileId": fileId } class MethodPowerpoint(MethodBase): """PowerPoint method implementation for presentation operations""" def __init__(self, serviceContainer: Any): """Initialize the PowerPoint method""" super().__init__(serviceContainer) self.name = "powerpoint" self.description = "Handle PowerPoint presentation operations like reading and creating slides" self.powerpointService = PowerpointService(serviceContainer) @action async def read(self, parameters: Dict[str, Any]) -> ActionResult: """ Read PowerPoint presentation Parameters: fileId (str): The ID of the PowerPoint file to read connectionReference (str): Reference to the Microsoft connection includeSlides (bool, optional): Whether to include slide content (default: True) """ try: fileId = parameters.get("fileId") connectionReference = parameters.get("connectionReference") includeSlides = parameters.get("includeSlides", True) if not fileId or not connectionReference: return self._createResult( success=False, data={}, error="File ID and connection reference are required" ) # Read presentation data = await self.powerpointService.readPresentation( fileId=fileId, connectionReference=connectionReference, includeSlides=includeSlides ) return self._createResult( success=True, data=data ) except Exception as e: logger.error(f"Error reading presentation: {str(e)}") return self._createResult( success=False, data={}, error=str(e) ) @action async def write(self, parameters: Dict[str, Any]) -> ActionResult: """ Write to PowerPoint presentation Parameters: fileId (str): The ID of the PowerPoint file to write to connectionReference (str): Reference to the Microsoft connection slides (List[Dict[str, Any]]): List of slides to write """ try: fileId = parameters.get("fileId") connectionReference = parameters.get("connectionReference") slides = parameters.get("slides", []) if not fileId or not connectionReference: return self._createResult( success=False, data={}, error="File ID and connection reference are required" ) # Write to presentation result = await self.powerpointService.writePresentation( fileId=fileId, connectionReference=connectionReference, slides=slides ) return self._createResult( success=True, data=result ) except Exception as e: logger.error(f"Error writing to presentation: {str(e)}") return self._createResult( success=False, data={}, error=str(e) ) @action async def convert(self, parameters: Dict[str, Any]) -> ActionResult: """ Convert PowerPoint presentation to another format Parameters: fileId (str): The ID of the PowerPoint file to convert connectionReference (str): Reference to the Microsoft connection format (str, optional): Target format (default: "pdf") """ try: fileId = parameters.get("fileId") connectionReference = parameters.get("connectionReference") format = parameters.get("format", "pdf") if not fileId or not connectionReference: return self._createResult( success=False, data={}, error="File ID and connection reference are required" ) # Convert presentation result = await self.powerpointService.convertPresentation( fileId=fileId, connectionReference=connectionReference, format=format ) return self._createResult( success=True, data=result ) except Exception as e: logger.error(f"Error converting presentation: {str(e)}") return self._createResult( success=False, data={}, error=str(e) ) @action async def createPresentation(self, parameters: Dict[str, Any]) -> ActionResult: """ Create new PowerPoint presentation Parameters: fileName (str): Name of the new presentation file connectionReference (str): Reference to the Microsoft connection template (str, optional): Template to use for the new presentation """ try: fileName = parameters.get("fileName") connectionReference = parameters.get("connectionReference") template = parameters.get("template") if not fileName or not connectionReference: return self._createResult( success=False, data={}, error="File name and connection reference are required" ) # Create presentation fileId = await self.powerpointService.createPresentation( fileName=fileName, connectionReference=connectionReference, template=template ) return self._createResult( success=True, data={"fileId": fileId} ) except Exception as e: logger.error(f"Error creating presentation: {str(e)}") return self._createResult( success=False, data={}, error=str(e) ) @action async def addSlide(self, parameters: Dict[str, Any]) -> ActionResult: """ Add slide to presentation Parameters: fileId (str): The ID of the PowerPoint file connectionReference (str): Reference to the Microsoft connection layout (str, optional): Slide layout type (default: "title") content (Dict[str, Any], optional): Content for the slide """ try: fileId = parameters.get("fileId") connectionReference = parameters.get("connectionReference") layout = parameters.get("layout", "title") content = parameters.get("content", {}) if not fileId or not connectionReference: return self._createResult( success=False, data={}, error="File ID and connection reference are required" ) # Add slide slide = await self.powerpointService.addSlide( fileId=fileId, connectionReference=connectionReference, layout=layout, content=content ) return self._createResult( success=True, data=slide ) except Exception as e: logger.error(f"Error adding slide: {str(e)}") return self._createResult( success=False, data={}, error=str(e) ) @action async def addContent(self, parameters: Dict[str, Any]) -> ActionResult: """ Add content to slide Parameters: fileId (str): The ID of the PowerPoint file connectionReference (str): Reference to the Microsoft connection slideId (str): ID of the slide to add content to content (Dict[str, Any]): Content to add to the slide """ try: fileId = parameters.get("fileId") connectionReference = parameters.get("connectionReference") slideId = parameters.get("slideId") content = parameters.get("content", {}) if not fileId or not connectionReference or not slideId: return self._createResult( success=False, data={}, error="File ID, connection reference, and slide ID are required" ) # Add content result = await self.powerpointService.addContent( fileId=fileId, connectionReference=connectionReference, slideId=slideId, content=content ) return self._createResult( success=True, data=result ) except Exception as e: logger.error(f"Error adding content: {str(e)}") return self._createResult( success=False, data={}, error=str(e) )