feat: 2-col reply buttons, direct send, language-matched prompts - Buttons: [回复N 直接发送] [回复N 编辑] in 2-column layout - CALLBACK_SEND_REPLY: send immediately without editing - Prompts enforce matching email language (no Chinese forced on English) - Full mode: greeting + comma only, no [Your Name] placeholder - Tests: 5/5 passing

This commit is contained in:
2026-07-17 20:49:11 +08:00
parent 6ce9a806ea
commit bedf5b887c
2 changed files with 35 additions and 4 deletions

View File

@@ -88,7 +88,10 @@ def expand_promo_detail(ai_cfg: AIConfig, sender: str, subject: str, body: str)
MORE_REPLIES_PROMPT_SHORT = """你是一个邮件回复助手。根据以下邮件内容生成3个完全不同的简洁回复。 MORE_REPLIES_PROMPT_SHORT = """你是一个邮件回复助手。根据以下邮件内容生成3个完全不同的简洁回复。
简洁模式:直接说要点,不需要称呼、落款、邮件格式。像微信/短信一样简短。 规则:
- 简洁模式:直接说要点,不需要称呼、落款、邮件格式。像微信/短信一样简短。
- 必须使用与邮件相同的语言回复(英文邮件用英文,中文邮件用中文)。
- 不要添加任何占位符如 [Your Name]。
以下是一些已经生成过的回复,请生成全新的回复,不要与已有回复重复。 以下是一些已经生成过的回复,请生成全新的回复,不要与已有回复重复。
@@ -105,7 +108,10 @@ MORE_REPLIES_PROMPT_SHORT = """你是一个邮件回复助手。根据以下邮
MORE_REPLIES_PROMPT_FULL = """你是一个邮件回复助手。根据以下邮件内容生成3个不同的正式邮件回复。 MORE_REPLIES_PROMPT_FULL = """你是一个邮件回复助手。根据以下邮件内容生成3个不同的正式邮件回复。
完整模式:生成完整的邮件格式,包括称呼、正文、落款。语气正式专业,适合商务邮件。 规则:
- 完整模式:生成完整的邮件格式,包括称呼(如 Dear xxx,)和正文,结尾只写问候语逗号(如 Best regards,)不写落款,用户会自己编辑添加。
- 必须使用与邮件相同的语言回复(英文邮件用英文,中文邮件用中文)。
- 语气正式专业,适合商务邮件。
以下是一些已经生成过的回复,请生成全新的回复,不要与已有回复重复。 以下是一些已经生成过的回复,请生成全新的回复,不要与已有回复重复。

View File

@@ -172,6 +172,7 @@ CALLBACK_REPLY = "r"
CALLBACK_AI_REPLY_FULL = "af" CALLBACK_AI_REPLY_FULL = "af"
CALLBACK_AI_REPLY_SHORT = "as" CALLBACK_AI_REPLY_SHORT = "as"
CALLBACK_SELECT_REPLY = "sr" CALLBACK_SELECT_REPLY = "sr"
CALLBACK_SEND_REPLY = "ss" # 直接发送(不需要编辑)
CALLBACK_REGEN = "rg" CALLBACK_REGEN = "rg"
CALLBACK_HINT = "h" CALLBACK_HINT = "h"
CALLBACK_CANCEL = "c" CALLBACK_CANCEL = "c"
@@ -230,8 +231,12 @@ def _cancel_keyboard() -> dict:
def _ai_reply_keyboard(msg_id: int, suggestions: list) -> dict: def _ai_reply_keyboard(msg_id: int, suggestions: list) -> dict:
kb = [] kb = []
for i, s in enumerate(suggestions): for i, s in enumerate(suggestions):
data = f"{CALLBACK_SELECT_REPLY}|{msg_id}|{i}" send_data = f"{CALLBACK_SEND_REPLY}|{msg_id}|{i}"
kb.append([{"text": s["text"][:30], "callback_data": data}]) edit_data = f"{CALLBACK_SELECT_REPLY}|{msg_id}|{i}"
kb.append([
{"text": f"回复{i+1} 直接发送", "callback_data": send_data},
{"text": f"回复{i+1} 编辑", "callback_data": edit_data},
])
kb.append([ kb.append([
{"text": "换一批", "callback_data": f"{CALLBACK_REGEN}|{msg_id}"}, {"text": "换一批", "callback_data": f"{CALLBACK_REGEN}|{msg_id}"},
{"text": "我想说:", "callback_data": f"{CALLBACK_HINT}|{msg_id}"}, {"text": "我想说:", "callback_data": f"{CALLBACK_HINT}|{msg_id}"},
@@ -385,6 +390,26 @@ def _handle_callback(tg_cfg: TelegramConfig, cfg: Config, cb: dict):
logger.info(" 显示 AI 回复建议 (%d 条, style=%s)", len(suggestions), style) logger.info(" 显示 AI 回复建议 (%d 条, style=%s)", len(suggestions), style)
_show_ai_suggestions(tg_cfg, chat_id, msg_id, suggestions) _show_ai_suggestions(tg_cfg, chat_id, msg_id, suggestions)
elif action == CALLBACK_SEND_REPLY and ctx:
idx = int(parts[2])
conv = load_conversation(chat_id)
if not conv or conv.get("summary_msg_id") != msg_id:
return
suggestions = conv.get("ai_suggestions", [])
if idx >= len(suggestions):
return
sel_text = suggestions[idx]["text"]
logger.info(" 直接发送回复 #%d: %s", idx, sel_text[:60])
_do_send_reply(tg_cfg, cfg, chat_id, msg_id, ctx, sel_text)
send_text(tg_cfg, str(chat_id), f"✅ 回复已发送")
delete_conversation(chat_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(can_reply),
})
elif action == CALLBACK_SELECT_REPLY and ctx: elif action == CALLBACK_SELECT_REPLY and ctx:
idx = int(parts[2]) idx = int(parts[2])
conv = load_conversation(chat_id) conv = load_conversation(chat_id)