refactor: merge reply suggestions into single AI request

- Summary prompt now includes can_reply + reply_suggestions fields
- Removed separate generate_reply_suggestions function and REPLY_PROMPT
- tg_bot reads reply suggestions from cached summary_data
- AI Reply button conditionally hidden based on can_reply
This commit is contained in:
2026-07-02 20:22:54 +08:00
parent 0dbc7ee661
commit 870ab4a59a
2 changed files with 50 additions and 85 deletions

View File

@@ -2,7 +2,7 @@ import json
import logging
import requests
from src.config import TelegramConfig, Config
from src.ai_client import generate_reply_suggestions
# reply suggestions come embedded in summary_data from summarize_email
from src.smtp_client import send_reply
from src.retry import retry
@@ -21,11 +21,12 @@ _conversations: dict[int, dict] = {}
@retry()
def send_summary(tg_cfg: TelegramConfig, chat_id: str, summary_text: str,
summary_data: dict, original_body: str, account_idx: int) -> int:
can_reply = summary_data.get("can_reply", True)
result = _tg_req(tg_cfg, "sendMessage", {
"chat_id": chat_id,
"text": summary_text,
"parse_mode": "MarkdownV2",
"reply_markup": _summary_keyboard(),
"reply_markup": _summary_keyboard(can_reply),
})
msg_id = result["result"]["message_id"]
_email_contexts[msg_id] = {
@@ -35,6 +36,7 @@ def send_summary(tg_cfg: TelegramConfig, chat_id: str, summary_text: str,
"account_idx": account_idx,
"sender": summary_data.get("sender", ""),
"subject": summary_data.get("subject", ""),
"can_reply": can_reply,
}
return msg_id
@@ -108,32 +110,28 @@ CALLBACK_CONFIRM_REPLY = "cr"
CALLBACK_CANCEL = "c"
def _summary_keyboard() -> dict:
return {
"inline_keyboard": [
[
{"text": "查看原文", "callback_data": CALLBACK_VIEW_ORIG},
{"text": "回复", "callback_data": CALLBACK_REPLY},
],
[
{"text": "AI回复", "callback_data": CALLBACK_AI_REPLY},
],
]
}
def _summary_keyboard(can_reply: bool = True) -> dict:
kb = [
[
{"text": "查看原文", "callback_data": CALLBACK_VIEW_ORIG},
{"text": "回复", "callback_data": CALLBACK_REPLY},
],
]
if can_reply:
kb.append([{"text": "AI回复", "callback_data": CALLBACK_AI_REPLY}])
return {"inline_keyboard": kb}
def _orig_keyboard() -> dict:
return {
"inline_keyboard": [
[
{"text": "返回摘要", "callback_data": CALLBACK_VIEW_SUMM},
{"text": "回复", "callback_data": CALLBACK_REPLY},
],
[
{"text": "AI回复", "callback_data": CALLBACK_AI_REPLY},
],
]
}
def _orig_keyboard(can_reply: bool = True) -> dict:
kb = [
[
{"text": "返回摘要", "callback_data": CALLBACK_VIEW_SUMM},
{"text": "回复", "callback_data": CALLBACK_REPLY},
],
]
if can_reply:
kb.append([{"text": "AI回复", "callback_data": CALLBACK_AI_REPLY}])
return {"inline_keyboard": kb}
def _ai_reply_keyboard(msg_id: int, suggestions: list) -> dict:
@@ -191,57 +189,52 @@ def _handle_callback(tg_cfg: TelegramConfig, cfg: Config, cb: dict):
ctx = _email_contexts.get(msg_id)
if action == CALLBACK_VIEW_ORIG and ctx:
can_reply = ctx.get("can_reply", True)
text = f"*📄 原文 \\- {_escape(ctx['subject'])}*\n\n{_escape(ctx['original_body'][:3500])}"
_tg_req(tg_cfg, "editMessageText", {
"chat_id": chat_id, "message_id": msg_id,
"text": text, "parse_mode": "MarkdownV2",
"reply_markup": _orig_keyboard(),
"reply_markup": _orig_keyboard(can_reply),
})
elif action == CALLBACK_VIEW_SUMM and ctx:
can_reply = ctx.get("can_reply", True)
_tg_req(tg_cfg, "editMessageText", {
"chat_id": chat_id, "message_id": msg_id,
"text": ctx["summary_text"], "parse_mode": "MarkdownV2",
"reply_markup": _summary_keyboard(),
"reply_markup": _summary_keyboard(can_reply),
})
elif action == CALLBACK_REPLY and ctx:
_conversations[chat_id] = {"state": "awaiting_reply", "msg_id": msg_id}
can_reply = ctx.get("can_reply", True)
_tg_req(tg_cfg, "editMessageText", {
"chat_id": chat_id, "message_id": msg_id,
"text": ctx["summary_text"], "parse_mode": "MarkdownV2",
"reply_markup": _summary_keyboard(),
"reply_markup": _summary_keyboard(can_reply),
})
send_text(tg_cfg, str(chat_id), "请输入你的回复内容:")
elif action == CALLBACK_AI_REPLY and ctx:
try:
acct = cfg.email_accounts[ctx["account_idx"]]
suggestions = generate_reply_suggestions(
cfg.ai, ctx["sender"], ctx["subject"], ctx["original_body"],
)
except Exception as e:
send_text(tg_cfg, str(chat_id), f"AI 生成回复失败: {e}")
return
if not suggestions.get("can_reply") or not suggestions.get("suggestions"):
suggestions = ctx["summary_data"].get("reply_suggestions", [])
if not suggestions:
send_text(tg_cfg, str(chat_id), "此邮件无需回复,已隐藏 AI 回复功能。")
return
_conversations[chat_id] = {
"state": "ai_reply_selection",
"msg_id": msg_id,
"ai_suggestions": suggestions["suggestions"],
"ai_suggestions": suggestions,
}
text = "*AI 建议回复,请选择:*\n\n"
for i, s in enumerate(suggestions["suggestions"], 1):
for i, s in enumerate(suggestions, 1):
tag = "✅ 可直接发送" if s.get("is_simple") else "📝 需确认"
text += f"{i}\\. {_escape(s['text'])} _{tag}_\n"
_tg_req(tg_cfg, "editMessageText", {
"chat_id": chat_id, "message_id": msg_id,
"text": text, "parse_mode": "MarkdownV2",
"reply_markup": _ai_reply_keyboard(msg_id, suggestions["suggestions"]),
"reply_markup": _ai_reply_keyboard(msg_id, suggestions),
})
elif action == CALLBACK_SELECT_REPLY and ctx:
@@ -282,10 +275,11 @@ def _handle_callback(tg_cfg: TelegramConfig, cfg: Config, cb: dict):
elif action == CALLBACK_CANCEL:
_conversations.pop(chat_id, None)
if ctx:
can_reply = ctx.get("can_reply", True)
_tg_req(tg_cfg, "editMessageText", {
"chat_id": chat_id, "message_id": msg_id,
"text": ctx["summary_text"], "parse_mode": "MarkdownV2",
"reply_markup": _summary_keyboard(),
"reply_markup": _summary_keyboard(can_reply),
})