feat: interactive inline buttons, SMTP reply, AI reply suggestions

- Inline keyboard: view original / back to summary toggle per message
- SMTP config per account for sending replies
- Reply button: click, type message, auto-send via SMTP
- AI Reply button: generates 3 suggestions via DeepSeek
  - Simple replies (OK, Got it) send immediately on tap
  - Substantive replies require confirm before send
  - Hides AI Reply button when AI determines reply unnecessary
- Telegram long polling integrated into main loop for real-time interaction
This commit is contained in:
2026-07-02 20:21:06 +08:00
parent 279f8c50e7
commit 0dbc7ee661
7 changed files with 410 additions and 48 deletions

View File

@@ -40,6 +40,49 @@ def summarize_email(ai_cfg: AIConfig, recipient: str, subject: str, sender: str,
"Content-Type": "application/json",
}
return _call_deepseek(ai_cfg, payload)
REPLY_PROMPT = """你是一个邮件回复助手。根据以下邮件内容生成3个建议回复。
返回 JSON:
{
"can_reply": true/false,
"suggestions": [
{"text": "回复内容", "is_simple": true/false},
{"text": "回复内容", "is_simple": true/false},
{"text": "回复内容", "is_simple": true/false}
]
}
- can_reply 为 false 时表示无需回复或不适合自动建议(此时忽略 suggestions
- is_simple 为 true 表示纯确认性回复(如"好的""收到""明白"),用户选择后可直接发送
- is_simple 为 false 表示涉及实质内容,需要用户确认后再发送
- 每条回复控制在 50 字以内
只返回 JSON不要包含任何其他文字。"""
@retry()
def generate_reply_suggestions(ai_cfg: AIConfig, sender: str, subject: str, body: str) -> dict:
content = f"发件人: {sender}\n主题: {subject}\n正文:\n{body[:4000]}"
payload = {
"model": ai_cfg.model,
"messages": [
{"role": "system", "content": REPLY_PROMPT},
{"role": "user", "content": content},
],
"response_format": {"type": "json_object"},
}
return _call_deepseek(ai_cfg, payload)
def _call_deepseek(ai_cfg: AIConfig, payload: dict) -> dict:
headers = {
"Authorization": f"Bearer {ai_cfg.api_key}",
"Content-Type": "application/json",
}
resp = requests.post(
f"{ai_cfg.base_url.rstrip('/')}/chat/completions",
headers=headers,