330 lines
No EOL
13 KiB
Python
330 lines
No EOL
13 KiB
Python
"""
|
|
Email Agent Module.
|
|
Handles email-related tasks using Microsoft Graph API.
|
|
"""
|
|
|
|
import logging
|
|
import json
|
|
from typing import Dict, Any, List, Optional
|
|
from ..workflow.agentBase import AgentBase
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class AgentEmail(AgentBase):
|
|
"""Agent for handling email-related tasks."""
|
|
|
|
def __init__(self):
|
|
"""Initialize the email agent."""
|
|
super().__init__()
|
|
self.name = "email"
|
|
self.label = "Email Agent"
|
|
self.description = "Handles email composition and sending using Microsoft Graph API"
|
|
self.capabilities = [
|
|
"email_composition",
|
|
"email_draft_creation",
|
|
"email_template_generation"
|
|
]
|
|
self.serviceBase = None
|
|
|
|
def setDependencies(self, serviceBase=None):
|
|
"""Set external dependencies for the agent."""
|
|
self.serviceBase = serviceBase
|
|
|
|
async def processTask(self, task: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""
|
|
Process an email-related task.
|
|
|
|
Args:
|
|
task: Task object containing:
|
|
- prompt: Instructions for the agent
|
|
- inputDocuments: List of documents to process
|
|
- outputSpecifications: List of required output documents
|
|
- context: Additional context including workflow info
|
|
|
|
Returns:
|
|
Dictionary containing:
|
|
- feedback: Text response explaining what was done
|
|
- documents: List of created documents
|
|
"""
|
|
try:
|
|
# Extract task information
|
|
prompt = task.get("prompt", "")
|
|
inputDocuments = task.get("inputDocuments", [])
|
|
outputSpecs = task.get("outputSpecifications", [])
|
|
|
|
# Check AI service
|
|
if not self.service.base:
|
|
return {
|
|
"feedback": "The Email agent requires an AI service to function.",
|
|
"documents": []
|
|
}
|
|
|
|
# Check if Microsoft connector is available
|
|
if not hasattr(self.service, 'msft'):
|
|
return {
|
|
"feedback": "Microsoft connector not available. Please ensure Microsoft integration is properly configured.",
|
|
"documents": []
|
|
}
|
|
|
|
# Get Microsoft token
|
|
token_data = self.service.msft.getMsftToken()
|
|
if not token_data:
|
|
# Create authentication trigger document
|
|
auth_doc = self._createFrontendAuthTriggerDocument()
|
|
return {
|
|
"feedback": "Microsoft authentication required. Please authenticate to continue.",
|
|
"documents": [auth_doc]
|
|
}
|
|
|
|
# Extract document data from input
|
|
documentContents, attachments = self._processInputDocuments(inputDocuments)
|
|
|
|
# Generate email subject and body using AI
|
|
emailTemplate = await self._generateEmailTemplate(prompt, documentContents)
|
|
|
|
# Create HTML preview of the email
|
|
htmlPreview = self._createHtmlPreview(emailTemplate)
|
|
|
|
# Attempt to create a draft email using Microsoft Graph API
|
|
draft_result = self.service.msft.createDraftEmail(
|
|
emailTemplate["recipient"],
|
|
emailTemplate["subject"],
|
|
emailTemplate["htmlBody"],
|
|
attachments
|
|
)
|
|
|
|
# Prepare output documents
|
|
documents = []
|
|
|
|
# Process output specifications
|
|
for spec in outputSpecs:
|
|
label = spec.get("label", "")
|
|
description = spec.get("description", "")
|
|
|
|
if label.endswith(".html"):
|
|
# Create the HTML template file
|
|
templateDoc = self.formatAgentDocumentOutput(
|
|
label,
|
|
emailTemplate["htmlBody"], # Use the actual HTML body, not the preview
|
|
"text/html"
|
|
)
|
|
documents.append(templateDoc)
|
|
elif label.endswith(".json"):
|
|
# Create JSON template if requested
|
|
templateJson = json.dumps(emailTemplate, indent=2)
|
|
templateDoc = self.formatAgentDocumentOutput(
|
|
label,
|
|
templateJson,
|
|
"application/json"
|
|
)
|
|
documents.append(templateDoc)
|
|
else:
|
|
# Default to preview for other cases
|
|
previewDoc = self.formatAgentDocumentOutput(
|
|
label,
|
|
htmlPreview,
|
|
"text/html"
|
|
)
|
|
documents.append(previewDoc)
|
|
|
|
# Prepare feedback message
|
|
if draft_result:
|
|
feedback = f"Email draft created successfully for {emailTemplate.get('recipient')}. The subject is: '{emailTemplate['subject']}'"
|
|
if attachments:
|
|
feedback += f" with {len(attachments)} attachment(s)"
|
|
feedback += ". You can open and edit it in your Outlook draft folder."
|
|
else:
|
|
feedback = "Email template created but could not save as draft. HTML preview and template are available as documents."
|
|
|
|
return {
|
|
"feedback": feedback,
|
|
"documents": documents
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error in email agent: {str(e)}")
|
|
return {
|
|
"feedback": f"Error processing email task: {str(e)}",
|
|
"documents": []
|
|
}
|
|
|
|
def _createFrontendAuthTriggerDocument(self) -> Dict[str, Any]:
|
|
"""Create a document that triggers Microsoft authentication in the frontend."""
|
|
return {
|
|
"name": "microsoft_auth",
|
|
"ext": "html",
|
|
"mimeType": "text/html",
|
|
"data": """
|
|
<div>
|
|
<h2>Microsoft Authentication Required</h2>
|
|
<p>Please click the button below to authenticate with Microsoft:</p>
|
|
<button onclick="window.location.href='/api/auth/microsoft'">Authenticate with Microsoft</button>
|
|
</div>
|
|
""",
|
|
"base64Encoded": False,
|
|
"metadata": {
|
|
"isText": True
|
|
}
|
|
}
|
|
|
|
def _processInputDocuments(self, input_docs: List[Dict[str, Any]]) -> tuple:
|
|
"""
|
|
Process input documents to extract content and prepare attachments.
|
|
|
|
Args:
|
|
input_docs: List of input documents
|
|
|
|
Returns:
|
|
Tuple of (document content text, list of attachments)
|
|
"""
|
|
documentContents = []
|
|
attachments = []
|
|
|
|
for doc in input_docs:
|
|
docName = doc.get("name", "unnamed")
|
|
if doc.get("ext"):
|
|
docName = f"{docName}.{doc.get('ext')}"
|
|
|
|
# Add document name to contents
|
|
documentContents.append(f"\n\n--- {docName} ---\n")
|
|
|
|
# Process document data directly
|
|
if doc.get("data"):
|
|
# Add to attachments with proper metadata
|
|
attachments.append({
|
|
"name": docName,
|
|
"document": {
|
|
"data": doc["data"],
|
|
"mimeType": doc.get("mimeType", "application/octet-stream"),
|
|
"base64Encoded": doc.get("base64Encoded", False)
|
|
}
|
|
})
|
|
documentContents.append(f"Document attached: {docName}")
|
|
else:
|
|
documentContents.append(f"Document referenced: {docName}")
|
|
|
|
return "\n".join(documentContents), attachments
|
|
|
|
async def _generateEmailTemplate(self, prompt: str, documentContents: str) -> Dict[str, Any]:
|
|
"""
|
|
Generate email template using AI.
|
|
|
|
Args:
|
|
prompt: The task prompt
|
|
documentContents: Extracted document content
|
|
|
|
Returns:
|
|
Email template dictionary with recipient, subject, body
|
|
"""
|
|
emailPrompt = f"""
|
|
Create an email based on the following request:
|
|
|
|
REQUEST: {prompt}
|
|
|
|
DOCUMENT CONTENTS:
|
|
{documentContents[:2000]}... (truncated if longer)
|
|
|
|
Generate an email template with:
|
|
1. A relevant recipient (use placeholder or derive from content if possible)
|
|
2. A concise but descriptive subject line
|
|
3. A professional HTML-formatted email body
|
|
4. Appropriate greeting and closing
|
|
|
|
Format your response as JSON with these fields:
|
|
- recipient: email address
|
|
- subject: subject line
|
|
- plainBody: plain text version
|
|
- htmlBody: HTML formatted version
|
|
|
|
Only return valid JSON. No preamble or explanations.
|
|
"""
|
|
|
|
try:
|
|
response = await self.service.base.callAi([
|
|
{"role": "system", "content": "You are an email template specialist. Create professional emails. Respond with valid JSON only."},
|
|
{"role": "user", "content": emailPrompt}
|
|
])
|
|
|
|
# Extract JSON from response
|
|
jsonStart = response.find('{')
|
|
jsonEnd = response.rfind('}') + 1
|
|
|
|
if jsonStart >= 0 and jsonEnd > jsonStart:
|
|
template = json.loads(response[jsonStart:jsonEnd])
|
|
return template
|
|
else:
|
|
# Fallback plan
|
|
logger.warning(f"Not able creating email template, generating fallback plan")
|
|
return {
|
|
"recipient": "recipient@example.com",
|
|
"subject": "Information Regarding Your Request",
|
|
"plainBody": f"This email is regarding your request: {prompt}",
|
|
"htmlBody": f"<html><body><p>This email is regarding your request: {prompt}</p></body></html>"
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.warning(f"Error generating email template: {str(e)}")
|
|
return {
|
|
"recipient": "recipient@example.com",
|
|
"subject": "Information Regarding Your Request",
|
|
"plainBody": f"This email is regarding your request: {prompt}",
|
|
"htmlBody": f"<html><body><p>This email is regarding your request: {prompt}</p></body></html>"
|
|
}
|
|
|
|
def _createHtmlPreview(self, emailTemplate: Dict[str, Any]) -> str:
|
|
"""
|
|
Create an HTML preview of the email template.
|
|
|
|
Args:
|
|
emailTemplate: Email template dictionary
|
|
|
|
Returns:
|
|
HTML string for preview
|
|
"""
|
|
html = f"""
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>Email Preview: {emailTemplate.get('subject', 'Email Template')}</title>
|
|
<style>
|
|
body {{ font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f5f5f5; }}
|
|
.email-container {{ max-width: 600px; margin: 20px auto; background-color: white; border: 1px solid #ddd; border-radius: 5px; overflow: hidden; }}
|
|
.email-header {{ background-color: #f0f0f0; padding: 15px; border-bottom: 1px solid #ddd; }}
|
|
.email-content {{ padding: 20px; }}
|
|
.email-footer {{ background-color: #f0f0f0; padding: 15px; border-top: 1px solid #ddd; font-size: 12px; color: #666; }}
|
|
.field {{ margin-bottom: 10px; }}
|
|
.field-label {{ font-weight: bold; color: #555; }}
|
|
.email-body {{ margin-top: 20px; padding-top: 20px; border-top: 1px solid #eee; }}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="email-container">
|
|
<div class="email-header">
|
|
<h2>Email Template Preview</h2>
|
|
</div>
|
|
<div class="email-content">
|
|
<div class="field">
|
|
<div class="field-label">To:</div>
|
|
<div>{emailTemplate.get('recipient', 'recipient@example.com')}</div>
|
|
</div>
|
|
<div class="field">
|
|
<div class="field-label">Subject:</div>
|
|
<div>{emailTemplate.get('subject', 'No Subject')}</div>
|
|
</div>
|
|
<div class="email-body">
|
|
{emailTemplate.get('htmlBody', '<p>No content</p>')}
|
|
</div>
|
|
</div>
|
|
<div class="email-footer">
|
|
<p>This is a preview of the email template. The actual email may appear differently in various email clients.</p>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
"""
|
|
return html
|
|
|
|
def getAgentEmail() -> AgentEmail:
|
|
"""Factory function to create and return an EmailAgent instance."""
|
|
return AgentEmail() |