Spaces:
Sleeping
Sleeping
| from schemas import ( | |
| FetchEmailsParams, | |
| ShowEmailParams, | |
| AnalyzeEmailsParams, | |
| DraftReplyParams, | |
| SendReplyParams, | |
| ) | |
| from typing import Any, Dict | |
| from email_scraper import scrape_emails_from_sender, _load_email_db, _save_email_db, _is_date_in_range | |
| from datetime import datetime | |
| from typing import List | |
| from openai import OpenAI | |
| import json | |
| from dotenv import load_dotenv | |
| import os | |
| # Load environment variables from .env file | |
| load_dotenv() | |
| # Initialize OpenAI client | |
| OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") | |
| client = OpenAI(api_key=OPENAI_API_KEY) | |
| def extract_date_range(query: str) -> Dict[str, str]: | |
| """ | |
| Use an LLM to extract a date range from a user query. | |
| Returns {"start_date":"DD-MMM-YYYY","end_date":"DD-MMM-YYYY"}. | |
| """ | |
| today_str = datetime.today().strftime("%d-%b-%Y") | |
| system_prompt = f""" | |
| You are a date‐range extractor. Today is {today_str}. | |
| Given a user query (in natural language), return _only_ valid JSON with: | |
| {{ | |
| "start_date": "DD-MMM-YYYY", | |
| "end_date": "DD-MMM-YYYY" | |
| }} | |
| Interpret relative dates as: | |
| - "today" → {today_str} to {today_str} | |
| - "yesterday" → 1 day ago to 1 day ago | |
| - "last week" → 7 days ago to {today_str} | |
| - "last month" → 30 days ago to {today_str} | |
| - "last N days" → N days ago to {today_str} | |
| Examples: | |
| - "emails from dev agarwal last week" | |
| → {{ "start_date": "01-Jun-2025", "end_date": "{today_str}" }} | |
| - "show me emails yesterday" | |
| → {{ "start_date": "06-Jun-2025", "end_date": "06-Jun-2025" }} | |
| Return _only_ the JSON object—no extra text. | |
| """ | |
| messages = [ | |
| {"role": "system", "content": system_prompt}, | |
| {"role": "user", "content": query} | |
| ] | |
| resp = client.chat.completions.create( | |
| model="gpt-4o-mini", | |
| temperature=0.0, | |
| messages=messages | |
| ) | |
| content = resp.choices[0].message.content.strip() | |
| # Try direct parse; if the model added fluff, strip to the JSON block. | |
| try: | |
| return json.loads(content) | |
| except json.JSONDecodeError: | |
| start = content.find("{") | |
| end = content.rfind("}") + 1 | |
| return json.loads(content[start:end]) | |
| def fetch_emails(email: str, query: str) -> Dict: | |
| """ | |
| Fetch emails from a sender within a date range extracted from the query. | |
| Now returns both date info and emails. | |
| Args: | |
| email: The sender's email address | |
| query: The original user query (for date extraction) | |
| Returns: | |
| Dict with date_info and emails | |
| """ | |
| # Extract date range from query | |
| date_info = extract_date_range(query) | |
| start_date = date_info.get("start_date") | |
| end_date = date_info.get("end_date") | |
| # Fetch emails using the existing scraper | |
| emails = scrape_emails_from_sender(email, start_date, end_date) | |
| # Return both date info and emails | |
| return { | |
| "date_info": date_info, | |
| "emails": emails, | |
| "email_count": len(emails) | |
| } | |
| def show_email(message_id: str) -> Dict: | |
| """ | |
| Retrieve the full email record (date, time, subject, content, etc.) | |
| from the local cache by message_id. | |
| """ | |
| db = _load_email_db() # returns { sender_email: { "emails": [...], "last_scraped": ... }, ... } | |
| # Search each sender's email list | |
| for sender_data in db.values(): | |
| for email in sender_data.get("emails", []): | |
| if email.get("message_id") == message_id: | |
| return email | |
| # If we didn't find it, raise or return an error structure | |
| raise ValueError(f"No email found with message_id '{message_id}'") | |
| def draft_reply(email: Dict, tone: str) -> str: | |
| # call LLM to generate reply | |
| # return a dummy reply for now | |
| print(f"Drafting reply for email {email['id']} with tone: {tone}") | |
| return f"Drafted reply for email {email['id']} with tone {tone}." | |
| ... | |
| def send_reply(message_id: str, reply_body: str) -> Dict: | |
| # SMTP / Gmail API send | |
| print(f"Sending reply to message {message_id} with body: {reply_body}") | |
| ... | |
| def analyze_emails(emails: List[Dict]) -> Dict: | |
| """ | |
| Summarize and extract insights from a list of emails. | |
| Returns a dict with this schema: | |
| { | |
| "summary": str, # a concise overview of all emails | |
| "insights": [str, ...] # list of key observations or stats | |
| } | |
| """ | |
| # 1) Prepare the email payload | |
| emails_payload = json.dumps(emails, ensure_ascii=False) | |
| # 2) Build the LLM prompt | |
| system_prompt = """ | |
| You are an expert email analyst. You will be given a JSON array of email objects, | |
| each with keys: date, time, subject, content, message_id. | |
| Your job is to produce _only_ valid JSON with two fields: | |
| 1. summary: a 1–2 sentence high-level overview of these emails. | |
| 2. insights: a list of 3–5 bullet-style observations or statistics | |
| (e.g. "2 job offers found", "overall positive tone", "next action: reply"). | |
| Output exactly: | |
| { | |
| "summary": "...", | |
| "insights": ["...", "...", ...] | |
| } | |
| """ | |
| messages = [ | |
| {"role": "system", "content": system_prompt}, | |
| {"role": "user", "content": f"Here are the emails:\n{emails_payload}"} | |
| ] | |
| # 3) Call the LLM | |
| response = client.chat.completions.create( | |
| model="gpt-4o-mini", | |
| temperature=0.0, | |
| messages=messages | |
| ) | |
| # 4) Parse and return | |
| content = response.choices[0].message.content.strip() | |
| try: | |
| return json.loads(content) | |
| except json.JSONDecodeError: | |
| # In case the model outputs extra text, extract the JSON block | |
| start = content.find('{') | |
| end = content.rfind('}') + 1 | |
| return json.loads(content[start:end]) | |
| TOOL_MAPPING = { | |
| "fetch_emails": fetch_emails, | |
| "show_email": show_email, | |
| "analyze_emails": analyze_emails, | |
| "draft_reply": draft_reply, | |
| "send_reply": send_reply, | |
| } |