handoff fixes
This commit is contained in:
parent
3f4bcc26d9
commit
6c52cd19cb
8 changed files with 561 additions and 59 deletions
198
backend/app/services/handoff_email_templates.py
Normal file
198
backend/app/services/handoff_email_templates.py
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
from html import escape
|
||||
|
||||
DEBUG_REASON_MARKERS = (
|
||||
"step passes",
|
||||
"classifier",
|
||||
"rule evaluation",
|
||||
"no handoff instructions were set",
|
||||
)
|
||||
|
||||
|
||||
def _clean_external_subject(subject: str) -> str:
|
||||
value = (subject or "(No subject)").strip()
|
||||
for prefix in ("Re: ", "RE: ", "re: "):
|
||||
if value.startswith(prefix):
|
||||
value = value[len(prefix) :].strip()
|
||||
for prefix in ("Human Handoff · ", "Human Handoff: "):
|
||||
if value.lower().startswith(prefix.lower()):
|
||||
value = value[len(prefix) :].strip()
|
||||
return value or "(No subject)"
|
||||
|
||||
|
||||
def handoff_email_subject(external_subject: str) -> str:
|
||||
return f"Human Handoff · {_clean_external_subject(external_subject)}"
|
||||
|
||||
|
||||
def external_reply_subject(external_subject: str) -> str:
|
||||
cleaned = _clean_external_subject(external_subject)
|
||||
return f"Re: {cleaned}"
|
||||
|
||||
|
||||
def format_reason_for_human(reason: str | None, *, is_update: bool = False) -> str | None:
|
||||
if is_update:
|
||||
return None
|
||||
if not reason or not reason.strip():
|
||||
return None
|
||||
lower = reason.strip().lower()
|
||||
if any(marker in lower for marker in DEBUG_REASON_MARKERS):
|
||||
return "This conversation matched your Human Handoff settings."
|
||||
if lower in {
|
||||
"matched handoff rules.",
|
||||
"new message in the linked conversation.",
|
||||
}:
|
||||
return "This conversation requires human attention."
|
||||
return reason.strip()
|
||||
|
||||
|
||||
def build_initial_handoff_text(
|
||||
*,
|
||||
external_sender: str,
|
||||
external_subject: str,
|
||||
external_body: str,
|
||||
agent_email: str,
|
||||
handoff_reason: str | None,
|
||||
) -> str:
|
||||
reason = format_reason_for_human(handoff_reason)
|
||||
lines = [
|
||||
"GENTIC — HUMAN HANDOFF",
|
||||
"",
|
||||
"This conversation needs your attention.",
|
||||
"",
|
||||
f"From: {external_sender}",
|
||||
f"Subject: {_clean_external_subject(external_subject)}",
|
||||
"",
|
||||
"LATEST MESSAGE",
|
||||
"",
|
||||
external_body.strip(),
|
||||
"",
|
||||
]
|
||||
if reason:
|
||||
lines.extend(["WHY YOU'RE RECEIVING THIS", "", reason, ""])
|
||||
lines.extend(
|
||||
[
|
||||
"REPLY NORMALLY",
|
||||
"",
|
||||
f"Reply to this email and Gentic will send only your new reply to the "
|
||||
f"original sender through {agent_email}.",
|
||||
"",
|
||||
"This private handoff conversation and quoted history will not be shared "
|
||||
"with the sender.",
|
||||
"",
|
||||
"—",
|
||||
"Gentic Human Handoff",
|
||||
"Private Agent ↔ Human conversation",
|
||||
]
|
||||
)
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def build_initial_handoff_html(
|
||||
*,
|
||||
external_sender: str,
|
||||
external_subject: str,
|
||||
external_body: str,
|
||||
agent_email: str,
|
||||
handoff_reason: str | None,
|
||||
) -> str:
|
||||
reason = format_reason_for_human(handoff_reason)
|
||||
reason_block = ""
|
||||
if reason:
|
||||
reason_block = f"""
|
||||
<tr><td style="padding:18px 24px 0;">
|
||||
<p style="margin:0 0 8px;font-size:11px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;color:#8A8695;">Why you're receiving this</p>
|
||||
<p style="margin:0;font-size:13px;line-height:1.55;color:#4A4652;">{escape(reason)}</p>
|
||||
</td></tr>"""
|
||||
|
||||
return f"""<!DOCTYPE html>
|
||||
<html><body style="margin:0;padding:0;background:#F5F4F2;">
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#F5F4F2;padding:24px 12px;">
|
||||
<tr><td align="center">
|
||||
<table role="presentation" width="600" cellpadding="0" cellspacing="0" style="width:100%;max-width:600px;background:#FFFFFF;border:1px solid #E8E5EA;border-radius:12px;overflow:hidden;">
|
||||
<tr><td style="padding:24px 24px 8px;">
|
||||
<p style="margin:0 0 4px;font-size:12px;font-weight:600;color:#7357F6;">✦ Gentic</p>
|
||||
<h1 style="margin:0 0 8px;font-size:20px;font-weight:700;color:#171424;">Human Handoff</h1>
|
||||
<p style="margin:0;font-size:13px;line-height:1.5;color:#77758A;">This conversation needs your attention.</p>
|
||||
</td></tr>
|
||||
<tr><td style="padding:8px 24px 0;">
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#FAF9FB;border:1px solid #ECE9EF;border-radius:8px;">
|
||||
<tr><td style="padding:14px;">
|
||||
<p style="margin:0 0 10px;font-size:10px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;color:#8A8695;">From</p>
|
||||
<p style="margin:0 0 14px;font-size:13px;font-weight:500;color:#292536;">{escape(external_sender)}</p>
|
||||
<p style="margin:0 0 10px;font-size:10px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;color:#8A8695;">Subject</p>
|
||||
<p style="margin:0;font-size:13px;font-weight:500;color:#292536;">{escape(_clean_external_subject(external_subject))}</p>
|
||||
</td></tr>
|
||||
</table>
|
||||
</td></tr>
|
||||
<tr><td style="padding:18px 24px 0;">
|
||||
<p style="margin:0 0 8px;font-size:11px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;color:#8A8695;">Latest message</p>
|
||||
<div style="border-left:3px solid #7357F6;padding:4px 0 4px 14px;font-size:14px;line-height:1.55;color:#292536;white-space:pre-wrap;">{escape(external_body.strip())}</div>
|
||||
</td></tr>
|
||||
{reason_block}
|
||||
<tr><td style="padding:18px 24px 24px;">
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#F7F5FF;border:1px solid #E5DFFF;border-radius:8px;">
|
||||
<tr><td style="padding:14px;">
|
||||
<p style="margin:0 0 8px;font-size:13px;font-weight:650;color:#5633CE;">↩ Reply normally</p>
|
||||
<p style="margin:0;font-size:12px;line-height:1.5;color:#625D70;">
|
||||
Reply to this email and Gentic will send your response to the original sender as
|
||||
<strong>{escape(agent_email)}</strong>. Only the new text you write will be sent.
|
||||
This handoff email and quoted history stay private.
|
||||
</p>
|
||||
</td></tr>
|
||||
</table>
|
||||
</td></tr>
|
||||
<tr><td style="padding:0 24px 24px;text-align:center;">
|
||||
<p style="margin:0;font-size:10px;color:#A19DAA;">Gentic Human Handoff<br>Private Agent ↔ Human conversation</p>
|
||||
</td></tr>
|
||||
</table>
|
||||
</td></tr>
|
||||
</table>
|
||||
</body></html>"""
|
||||
|
||||
|
||||
def build_update_handoff_text(
|
||||
*,
|
||||
external_sender: str,
|
||||
external_body: str,
|
||||
agent_email: str,
|
||||
) -> str:
|
||||
return "\n".join(
|
||||
[
|
||||
"Customer replied",
|
||||
"",
|
||||
external_sender,
|
||||
"",
|
||||
external_body.strip(),
|
||||
"",
|
||||
f"Reply normally to respond through {agent_email}.",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def build_update_handoff_html(
|
||||
*,
|
||||
external_sender: str,
|
||||
external_body: str,
|
||||
agent_email: str,
|
||||
) -> str:
|
||||
return f"""<!DOCTYPE html>
|
||||
<html><body style="margin:0;padding:0;background:#F5F4F2;">
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#F5F4F2;padding:24px 12px;">
|
||||
<tr><td align="center">
|
||||
<table role="presentation" width="600" cellpadding="0" cellspacing="0" style="width:100%;max-width:600px;background:#FFFFFF;border:1px solid #E8E5EA;border-radius:12px;">
|
||||
<tr><td style="padding:24px;">
|
||||
<p style="margin:0 0 8px;font-size:12px;font-weight:600;color:#7357F6;">✦ Gentic</p>
|
||||
<h1 style="margin:0 0 16px;font-size:18px;font-weight:700;color:#171424;">Customer replied</h1>
|
||||
<p style="margin:0 0 12px;font-size:13px;font-weight:500;color:#292536;">{escape(external_sender)}</p>
|
||||
<div style="border-left:3px solid #7357F6;padding:4px 0 4px 14px;font-size:14px;line-height:1.55;color:#292536;white-space:pre-wrap;">{escape(external_body.strip())}</div>
|
||||
<p style="margin:18px 0 0;font-size:12px;line-height:1.5;color:#625D70;">
|
||||
Reply normally to respond through <strong>{escape(agent_email)}</strong>.
|
||||
</p>
|
||||
</td></tr>
|
||||
</table>
|
||||
</td></tr>
|
||||
</table>
|
||||
</body></html>"""
|
||||
|
||||
|
||||
def build_relay_confirmation_text(customer_body: str) -> str:
|
||||
return f"Sent to customer:\n\n{customer_body.strip()}"
|
||||
Loading…
Add table
Add a link
Reference in a new issue