feat: complete Phase 1 MVP - add agent panel, email notifications, and PDF invoicing
Agent Panel UI: - Visual dashboard showing all agents with status (idle/working) - Agent cards with stats: total tasks, completed tasks - Capabilities display for each agent - Execution log showing recent agent activity - Real-time updates (refreshes every 10 seconds) - Click agent to filter execution log - Status indicators: pending, running, completed, failed, pending_approval Email Notifications: - SMTP email service with HTML templates - Task assignment notifications - Agent approval request emails with action details - Agent completion notifications - Configurable via environment variables (SMTP_HOST, SMTP_USER, SMTP_PASSWORD, SMTP_TO) - Integrated with agent executor service PDF Invoice Generation: - Professional PDF invoices using ReportLab - Generate from time entries by project - Client information and company branding - Itemized time entries with hours, rates, amounts - Automatic totals calculation - Download as PDF attachment - API endpoints: /api/opc/invoices/generate - Projects and clients management endpoints Additional Features: - Projects CRUD API - Clients CRUD API - Project time tracking summary - Billable vs non-billable hours tracking Phase 1 MVP 100% Complete: ✅ PostgreSQL database with full schema ✅ Task CRUD API with automatic time tracking ✅ Kanban board with drag-and-drop ✅ Agent executor service with LLM integration ✅ WebSocket real-time updates ✅ 3 core agents (PM, CTO, COO) ✅ Agent panel UI with execution logs ✅ Email notifications (Telegram + Email) ✅ PDF invoice generation All 12 tasks completed!
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
"""
|
||||
Email notification service for OPC
|
||||
"""
|
||||
import os
|
||||
import smtplib
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Email configuration from environment
|
||||
SMTP_HOST = os.getenv("SMTP_HOST", "smtp.gmail.com")
|
||||
SMTP_PORT = int(os.getenv("SMTP_PORT", "587"))
|
||||
SMTP_USER = os.getenv("SMTP_USER", "")
|
||||
SMTP_PASSWORD = os.getenv("SMTP_PASSWORD", "")
|
||||
SMTP_FROM = os.getenv("SMTP_FROM", SMTP_USER)
|
||||
SMTP_TO = os.getenv("SMTP_TO", "")
|
||||
|
||||
|
||||
async def send_email(subject: str, body: str, html_body: str = None):
|
||||
"""Send email notification"""
|
||||
if not SMTP_USER or not SMTP_PASSWORD or not SMTP_TO:
|
||||
logger.warning("Email not configured, skipping notification")
|
||||
return
|
||||
|
||||
try:
|
||||
# Create message
|
||||
msg = MIMEMultipart("alternative")
|
||||
msg["Subject"] = subject
|
||||
msg["From"] = SMTP_FROM
|
||||
msg["To"] = SMTP_TO
|
||||
|
||||
# Add plain text part
|
||||
msg.attach(MIMEText(body, "plain"))
|
||||
|
||||
# Add HTML part if provided
|
||||
if html_body:
|
||||
msg.attach(MIMEText(html_body, "html"))
|
||||
|
||||
# Send email
|
||||
with smtplib.SMTP(SMTP_HOST, SMTP_PORT) as server:
|
||||
server.starttls()
|
||||
server.login(SMTP_USER, SMTP_PASSWORD)
|
||||
server.send_message(msg)
|
||||
|
||||
logger.info(f"Email sent: {subject}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send email: {e}")
|
||||
|
||||
|
||||
async def send_task_assigned_email(task: dict, assigned_to: str, assigned_type: str):
|
||||
"""Send email when task is assigned"""
|
||||
subject = f"Task Assigned: {task['title']}"
|
||||
|
||||
body = f"""You have been assigned a new task:
|
||||
|
||||
Title: {task['title']}
|
||||
Priority: {task['priority']}
|
||||
Status: {task['status']}
|
||||
|
||||
Description:
|
||||
{task.get('description', 'No description')}
|
||||
|
||||
View task: https://nas.jimmygan.com/?page=opc
|
||||
"""
|
||||
|
||||
html_body = f"""
|
||||
<html>
|
||||
<body>
|
||||
<h2>Task Assigned</h2>
|
||||
<p>You have been assigned a new task:</p>
|
||||
<table style="border-collapse: collapse; margin: 20px 0;">
|
||||
<tr><td style="padding: 8px; font-weight: bold;">Title:</td><td style="padding: 8px;">{task['title']}</td></tr>
|
||||
<tr><td style="padding: 8px; font-weight: bold;">Priority:</td><td style="padding: 8px;">{task['priority']}</td></tr>
|
||||
<tr><td style="padding: 8px; font-weight: bold;">Status:</td><td style="padding: 8px;">{task['status']}</td></tr>
|
||||
</table>
|
||||
<p><strong>Description:</strong></p>
|
||||
<p>{task.get('description', 'No description')}</p>
|
||||
<p><a href="https://nas.jimmygan.com/?page=opc" style="background-color: #4F46E5; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px; display: inline-block;">View Task</a></p>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
await send_email(subject, body, html_body)
|
||||
|
||||
|
||||
async def send_agent_approval_email(agent_name: str, task: dict, reasoning: str, actions: list):
|
||||
"""Send email for agent approval request"""
|
||||
subject = f"Agent Approval Required: {agent_name}"
|
||||
|
||||
actions_text = "\n".join([f"- {action.get('type')}: {action.get('title', action.get('message', 'N/A'))}" for action in actions])
|
||||
|
||||
body = f"""Agent approval required:
|
||||
|
||||
Agent: {agent_name}
|
||||
Task: {task['title']}
|
||||
|
||||
Reasoning:
|
||||
{reasoning}
|
||||
|
||||
Proposed Actions:
|
||||
{actions_text}
|
||||
|
||||
Please review and approve in the OPC dashboard:
|
||||
https://nas.jimmygan.com/?page=opc
|
||||
"""
|
||||
|
||||
html_body = f"""
|
||||
<html>
|
||||
<body>
|
||||
<h2>🤖 Agent Approval Required</h2>
|
||||
<p><strong>Agent:</strong> {agent_name}</p>
|
||||
<p><strong>Task:</strong> {task['title']}</p>
|
||||
<h3>Reasoning:</h3>
|
||||
<p>{reasoning}</p>
|
||||
<h3>Proposed Actions:</h3>
|
||||
<ul>
|
||||
{"".join([f"<li><strong>{action.get('type')}</strong>: {action.get('title', action.get('message', 'N/A'))}</li>" for action in actions])}
|
||||
</ul>
|
||||
<p><a href="https://nas.jimmygan.com/?page=opc" style="background-color: #4F46E5; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px; display: inline-block;">Review & Approve</a></p>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
await send_email(subject, body, html_body)
|
||||
|
||||
|
||||
async def send_agent_completion_email(agent_name: str, task: dict, actions_count: int):
|
||||
"""Send email when agent completes task"""
|
||||
subject = f"Agent Task Completed: {agent_name}"
|
||||
|
||||
body = f"""Agent has completed a task:
|
||||
|
||||
Agent: {agent_name}
|
||||
Task: {task['title']}
|
||||
Actions Executed: {actions_count}
|
||||
|
||||
View details: https://nas.jimmygan.com/?page=opc
|
||||
"""
|
||||
|
||||
html_body = f"""
|
||||
<html>
|
||||
<body>
|
||||
<h2>✅ Agent Task Completed</h2>
|
||||
<p><strong>Agent:</strong> {agent_name}</p>
|
||||
<p><strong>Task:</strong> {task['title']}</p>
|
||||
<p><strong>Actions Executed:</strong> {actions_count}</p>
|
||||
<p><a href="https://nas.jimmygan.com/?page=opc" style="background-color: #10B981; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px; display: inline-block;">View Details</a></p>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
await send_email(subject, body, html_body)
|
||||
Reference in New Issue
Block a user