Spaces:
Running
Running
| """ | |
| feed.py — Feedback email sender for STEM Copilot. | |
| Uses Resend API to deliver user feedback to subhansh.stembotix@gmail.com. | |
| Set RESEND_API_KEY in HF Secrets (or .env) before deploying. | |
| """ | |
| import os | |
| import base64 | |
| import resend #type:ignore | |
| RESEND_API_KEY = os.environ.get("RESEND_API_KEY", "") | |
| resend.api_key = RESEND_API_KEY | |
| TO_EMAIL = "subhansh.stembotix@gmail.com" | |
| FROM_EMAIL = "STEM Copilot <onboarding@resend.dev>" | |
| _STAR_FILLED = "★" | |
| _STAR_EMPTY = "☆" | |
| _RATING_LABELS = {"1": "Poor", "2": "Fair", "3": "Good", "4": "Excellent"} | |
| _CATEGORY_LABELS = { | |
| "bug": "Technical Issue / Bug Report", | |
| "feature": "Feature Request", | |
| "iam": "IAM / Access Policy", | |
| "documentation": "Documentation", | |
| "other": "Other", | |
| } | |
| def _stars(n: int, max_n: int = 5) -> str: | |
| return _STAR_FILLED * n + _STAR_EMPTY * (max_n - n) | |
| def _row(label: str, value: str) -> str: | |
| return f""" | |
| <tr> | |
| <td style="padding:8px 14px;color:#888;font-size:13px;white-space:nowrap;">{label}</td> | |
| <td style="padding:8px 14px;color:#e8e8e8;font-size:14px;">{value}</td> | |
| </tr>""" | |
| def build_html( | |
| user_id: str, | |
| user_email: str, | |
| user_name: str, | |
| overall: int, | |
| ease: int, | |
| quality: int, | |
| category: str, | |
| message: str, | |
| ) -> str: | |
| overall_stars = _stars(overall) | |
| ease_label = _RATING_LABELS.get(str(ease), str(ease)) | |
| quality_label = _RATING_LABELS.get(str(quality), str(quality)) | |
| category_label = _CATEGORY_LABELS.get(category, category) | |
| display_from = user_email or user_id | |
| if user_name and user_name.strip(): | |
| display_from = f"{user_name.strip()} <{user_email}>" if user_email else user_name.strip() | |
| return f"""<!DOCTYPE html> | |
| <html> | |
| <head><meta charset="UTF-8"></head> | |
| <body style="margin:0;padding:0;background:#0a0a0a;font-family:'Segoe UI',sans-serif;"> | |
| <table width="100%" cellpadding="0" cellspacing="0" style="background:#0a0a0a;padding:32px 0;"> | |
| <tr><td align="center"> | |
| <table width="600" cellpadding="0" cellspacing="0" | |
| style="background:#141414;border:1px solid #222;border-radius:16px;overflow:hidden;"> | |
| <!-- Header --> | |
| <tr> | |
| <td style="background:linear-gradient(135deg,#EC642B,#f07d4a);padding:24px 32px;"> | |
| <h1 style="margin:0;color:#fff;font-size:22px;font-weight:700;"> | |
| New Feedback — STEM Copilot | |
| </h1> | |
| </td> | |
| </tr> | |
| <!-- Ratings table --> | |
| <tr><td style="padding:24px 32px 8px;"> | |
| <table cellpadding="0" cellspacing="0" width="100%" | |
| style="background:#1a1a1a;border:1px solid #2a2a2a;border-radius:10px;"> | |
| {_row("From", display_from)} | |
| {_row("Overall Rating", f"<span style='font-size:18px;letter-spacing:2px;color:#f5a623;'>{overall_stars}</span> ({overall}/5)")} | |
| {_row("Ease of Use", ease_label)} | |
| {_row("Response Quality", quality_label)} | |
| {_row("Category", category_label)} | |
| </table> | |
| </td></tr> | |
| <!-- Message body --> | |
| <tr><td style="padding:16px 32px;"> | |
| <div style="background:#1a1a1a;border:1px solid #2a2a2a;border-radius:10px;padding:16px;"> | |
| <p style="margin:0 0 6px;color:#888;font-size:12px;text-transform:uppercase;letter-spacing:1px;">Message</p> | |
| <p style="margin:0;color:#e8e8e8;font-size:14px;line-height:1.7;white-space:pre-wrap;">{message}</p> | |
| </div> | |
| </td></tr> | |
| <!-- Footer --> | |
| <tr><td style="padding:16px 32px 28px;"> | |
| <p style="margin:0;color:#555;font-size:11px;text-align:center;"> | |
| Sent automatically by STEM Copilot · STEMbotix | |
| </p> | |
| </td></tr> | |
| </table> | |
| </td></tr> | |
| </table> | |
| </body> | |
| </html>""" | |
| def send_feedback( | |
| user_id: str, | |
| user_email: str, | |
| user_name: str = "", | |
| overall: int = 5, | |
| ease: int = 4, | |
| quality: int = 4, | |
| category: str = "other", | |
| message: str = "", | |
| attachments: list[dict] | None = None, | |
| ) -> bool: | |
| """ | |
| Send feedback email via Resend. | |
| attachments: list of {"filename": str, "content": base64_str, "content_type": str} | |
| Returns True on success. | |
| """ | |
| if not RESEND_API_KEY: | |
| print("[FEED] RESEND_API_KEY not set — skipping email.") | |
| return False | |
| html = build_html(user_id, user_email, user_name, overall, ease, quality, category, message) | |
| payload: dict = { | |
| "from": FROM_EMAIL, | |
| "to": [TO_EMAIL], | |
| "subject": f"[STEM Copilot Feedback] {_CATEGORY_LABELS.get(category, category)} ({overall}/5)", | |
| "html": html, | |
| } | |
| if attachments: | |
| payload["attachments"] = [ | |
| { | |
| "filename": a["filename"], | |
| "content": a["content"], # base64 string | |
| } | |
| for a in attachments[:5] # cap at 5 | |
| ] | |
| try: | |
| resp = resend.Emails.send(payload) | |
| print(f"[FEED] Email sent — id={resp.get('id')}") | |
| return True | |
| except Exception as exc: | |
| print(f"[FEED] Exception: {exc}") | |
| return False | |