feat: show thinking indicator during AI processing, accept non-numeric verification codes
This commit is contained in:
@@ -31,7 +31,7 @@ SYSTEM_PROMPT = """你是一个邮件摘要助手。请分析邮件内容并以
|
|||||||
其中:
|
其中:
|
||||||
- subject 不能照抄原邮件主题,必须是你总结生成的简短标题
|
- subject 不能照抄原邮件主题,必须是你总结生成的简短标题
|
||||||
- category: 邮件类型。推广邮件(营销、促销、广告、 newsletter 推广等)标记为 "promotion";验证码邮件标记为 "notification";普通邮件标记为 "normal"
|
- category: 邮件类型。推广邮件(营销、促销、广告、 newsletter 推广等)标记为 "promotion";验证码邮件标记为 "notification";普通邮件标记为 "normal"
|
||||||
- verification_code: 仅当 category 为 "notification" 且邮件包含验证码时填写,只填纯数字验证码(如「123456」「888888」),不包含平台名。非验证码邮件留空字符串
|
- verification_code: 仅当 category 为 "notification" 且邮件包含验证码时填写,提取完整的验证码字符串(纯数字、字母数字混合等均可,如「123456」「abc-789」「701383」)。非验证码邮件留空字符串
|
||||||
- app_name: 仅当 category 为 "notification" 且邮件包含验证码时填写,填写发送验证码的应用/平台名称(如「GitHub」「微信」「Kraken」)。非验证码邮件留空字符串
|
- app_name: 仅当 category 为 "notification" 且邮件包含验证码时填写,填写发送验证码的应用/平台名称(如「GitHub」「微信」「Kraken」)。非验证码邮件留空字符串
|
||||||
- links: 从邮件正文中提取用户需要点击的链接。只提取有意义的、用户可能需要操作的链接(如确认链接、登录链接、下载链接、账单链接、退订链接等),不要提取、追踪像素、邮件头中的无关链接。每条包含简短描述文字和完整 URL。最多 3 条。没有需要点击的链接时为空数组
|
- links: 从邮件正文中提取用户需要点击的链接。只提取有意义的、用户可能需要操作的链接(如确认链接、登录链接、下载链接、账单链接、退订链接等),不要提取、追踪像素、邮件头中的无关链接。每条包含简短描述文字和完整 URL。最多 3 条。没有需要点击的链接时为空数组
|
||||||
- summary 要简洁,把需要知道的信息浓缩成一句话,不需要分条列出
|
- summary 要简洁,把需要知道的信息浓缩成一句话,不需要分条列出
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from typing import Generator, Optional
|
|||||||
from src.config import Config
|
from src.config import Config
|
||||||
from src.email_client import fetch_unseen_emails, mark_as_seen, Email
|
from src.email_client import fetch_unseen_emails, mark_as_seen, Email
|
||||||
from src.ai_client import summarize_email
|
from src.ai_client import summarize_email
|
||||||
from src.tg_bot import send_summary, format_summary
|
from src.tg_bot import send_summary, send_thinking, edit_message, format_summary
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -23,6 +23,10 @@ def poll_accounts(cfg: Config) -> Generator[tuple[int, Email], None, None]:
|
|||||||
|
|
||||||
def ai_process(cfg: Config, acct_idx: int, mail: Email) -> Optional[dict]:
|
def ai_process(cfg: Config, acct_idx: int, mail: Email) -> Optional[dict]:
|
||||||
logger.info(f" 正在摘要: {mail.subject}")
|
logger.info(f" 正在摘要: {mail.subject}")
|
||||||
|
# 先发思考中消息
|
||||||
|
thinking_msg_id = send_thinking(cfg.telegram, cfg.telegram.chat_id)
|
||||||
|
logger.info(f" 思考中消息已发送: msg_id={thinking_msg_id}")
|
||||||
|
|
||||||
summary = summarize_email(cfg.ai, mail.recipient, mail.subject, mail.sender, mail.body, mail.account_email)
|
summary = summarize_email(cfg.ai, mail.recipient, mail.subject, mail.sender, mail.body, mail.account_email)
|
||||||
summary["recipient"] = mail.recipient
|
summary["recipient"] = mail.recipient
|
||||||
if "@" not in summary.get("sender", ""):
|
if "@" not in summary.get("sender", ""):
|
||||||
@@ -38,11 +42,37 @@ def ai_process(cfg: Config, acct_idx: int, mail: Email) -> Optional[dict]:
|
|||||||
"acct_idx": acct_idx,
|
"acct_idx": acct_idx,
|
||||||
"account_email": mail.account_email,
|
"account_email": mail.account_email,
|
||||||
"uid": mail.uid,
|
"uid": mail.uid,
|
||||||
|
"thinking_msg_id": thinking_msg_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def tg_send_and_mark(cfg: Config, info: dict):
|
def tg_send_and_mark(cfg: Config, info: dict):
|
||||||
acct = cfg.email_accounts[info["acct_idx"]]
|
acct = cfg.email_accounts[info["acct_idx"]]
|
||||||
|
thinking_msg_id = info.get("thinking_msg_id")
|
||||||
|
if thinking_msg_id:
|
||||||
|
# 编辑思考中消息为实际摘要
|
||||||
|
from src.tg_bot import _summary_keyboard
|
||||||
|
can_reply = info["data"].get("can_reply", True)
|
||||||
|
is_promotion = info["data"].get("category") == "promotion"
|
||||||
|
links = info["data"].get("links", [])
|
||||||
|
edit_message(cfg.telegram, cfg.telegram.chat_id, thinking_msg_id,
|
||||||
|
info["text"], _summary_keyboard(can_reply, is_promotion, links))
|
||||||
|
# 保存上下文用于回调
|
||||||
|
from src.tg_bot import _email_contexts
|
||||||
|
_email_contexts[thinking_msg_id] = {
|
||||||
|
"summary_text": info["text"],
|
||||||
|
"summary_data": info["data"],
|
||||||
|
"original_body": info["original_body"][:10000],
|
||||||
|
"original_sender": info.get("original_sender", ""),
|
||||||
|
"original_recipient": info.get("original_recipient", ""),
|
||||||
|
"original_reply_to": info.get("original_reply_to", ""),
|
||||||
|
"account_email": info.get("account_email", ""),
|
||||||
|
"sender": info["data"].get("sender", ""),
|
||||||
|
"subject": info["data"].get("subject", ""),
|
||||||
|
"can_reply": can_reply,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
# 兜底:没有思考中消息就正常发送
|
||||||
send_summary(cfg.telegram, cfg.telegram.chat_id,
|
send_summary(cfg.telegram, cfg.telegram.chat_id,
|
||||||
info["text"], info["data"],
|
info["text"], info["data"],
|
||||||
info["original_body"], info["acct_idx"],
|
info["original_body"], info["acct_idx"],
|
||||||
@@ -50,6 +80,6 @@ def tg_send_and_mark(cfg: Config, info: dict):
|
|||||||
original_recipient=info.get("original_recipient", ""),
|
original_recipient=info.get("original_recipient", ""),
|
||||||
original_reply_to=info.get("original_reply_to", ""),
|
original_reply_to=info.get("original_reply_to", ""),
|
||||||
account_email=info.get("account_email", ""))
|
account_email=info.get("account_email", ""))
|
||||||
# 只有 send_summary 成功返回(未抛异常)才标记已读
|
# 只有发送成功才标记已读
|
||||||
mark_as_seen(acct, [info["uid"]])
|
mark_as_seen(acct, [info["uid"]])
|
||||||
logger.info(f" TG 发送成功,已标记已读")
|
logger.info(f" TG 发送成功,已标记已读")
|
||||||
|
|||||||
@@ -57,6 +57,25 @@ def send_text(tg_cfg: TelegramConfig, chat_id: str, text: str,
|
|||||||
return result["result"]["message_id"]
|
return result["result"]["message_id"]
|
||||||
|
|
||||||
|
|
||||||
|
def send_thinking(tg_cfg: TelegramConfig, chat_id: str) -> int:
|
||||||
|
result = _tg_req(tg_cfg, "sendMessage", {
|
||||||
|
"chat_id": chat_id,
|
||||||
|
"text": "💭 思考中...",
|
||||||
|
})
|
||||||
|
return result["result"]["message_id"]
|
||||||
|
|
||||||
|
|
||||||
|
def edit_message(tg_cfg: TelegramConfig, chat_id: str, msg_id: int,
|
||||||
|
text: str, reply_markup: dict = None):
|
||||||
|
payload = {
|
||||||
|
"chat_id": chat_id, "message_id": msg_id,
|
||||||
|
"text": text, "parse_mode": "MarkdownV2",
|
||||||
|
}
|
||||||
|
if reply_markup:
|
||||||
|
payload["reply_markup"] = reply_markup
|
||||||
|
_tg_req(tg_cfg, "editMessageText", payload)
|
||||||
|
|
||||||
|
|
||||||
def delete_message(tg_cfg: TelegramConfig, chat_id: str, msg_id: int):
|
def delete_message(tg_cfg: TelegramConfig, chat_id: str, msg_id: int):
|
||||||
try:
|
try:
|
||||||
_tg_req(tg_cfg, "deleteMessage", {
|
_tg_req(tg_cfg, "deleteMessage", {
|
||||||
@@ -325,6 +344,11 @@ def _handle_callback(tg_cfg: TelegramConfig, cfg: Config, cb: dict):
|
|||||||
if not conv or conv.get("summary_msg_id") != msg_id:
|
if not conv or conv.get("summary_msg_id") != msg_id:
|
||||||
return
|
return
|
||||||
logger.info(" 换一批 (已有 %d 条历史)", len(conv.get("all_suggestions", [])))
|
logger.info(" 换一批 (已有 %d 条历史)", len(conv.get("all_suggestions", [])))
|
||||||
|
# 先显示思考中
|
||||||
|
_tg_req(tg_cfg, "editMessageText", {
|
||||||
|
"chat_id": chat_id, "message_id": msg_id,
|
||||||
|
"text": "💭 思考中...", "parse_mode": None,
|
||||||
|
})
|
||||||
try:
|
try:
|
||||||
new_suggestions = generate_more_replies(
|
new_suggestions = generate_more_replies(
|
||||||
cfg.ai, ctx["sender"], ctx["subject"], ctx["original_body"],
|
cfg.ai, ctx["sender"], ctx["subject"], ctx["original_body"],
|
||||||
@@ -365,6 +389,11 @@ def _handle_callback(tg_cfg: TelegramConfig, cfg: Config, cb: dict):
|
|||||||
logger.info(" 取消, 删除提示消息")
|
logger.info(" 取消, 删除提示消息")
|
||||||
|
|
||||||
elif action == CALLBACK_PROMO_DETAIL and ctx:
|
elif action == CALLBACK_PROMO_DETAIL and ctx:
|
||||||
|
# 先显示思考中
|
||||||
|
_tg_req(tg_cfg, "editMessageText", {
|
||||||
|
"chat_id": chat_id, "message_id": msg_id,
|
||||||
|
"text": "💭 思考中...", "parse_mode": None,
|
||||||
|
})
|
||||||
try:
|
try:
|
||||||
details = expand_promo_detail(
|
details = expand_promo_detail(
|
||||||
cfg.ai, ctx["sender"], ctx["subject"], ctx["original_body"],
|
cfg.ai, ctx["sender"], ctx["subject"], ctx["original_body"],
|
||||||
|
|||||||
Reference in New Issue
Block a user