diff --git a/backend/app/agent_subgraphs/contact/__init__.py b/backend/app/agent_subgraphs/contact/__init__.py index 4cb5e87..cb55dd4 100644 --- a/backend/app/agent_subgraphs/contact/__init__.py +++ b/backend/app/agent_subgraphs/contact/__init__.py @@ -1,6 +1,6 @@ """ -通讯录子图 -Contact Subgraph Module +通讯录子图 - 完善版 +Contact Subgraph Module - Complete """ from .state import ( @@ -22,6 +22,7 @@ from .nodes import ( format_result, should_continue ) +from .api_client import contact_api, ContactAPIClient __all__ = [ # State @@ -43,5 +44,9 @@ __all__ = [ "send_email", "sniff_contacts", "format_result", - "should_continue" + "should_continue", + + # API + "contact_api", + "ContactAPIClient" ] diff --git a/backend/app/agent_subgraphs/contact/api_client.py b/backend/app/agent_subgraphs/contact/api_client.py new file mode 100644 index 0000000..cce155f --- /dev/null +++ b/backend/app/agent_subgraphs/contact/api_client.py @@ -0,0 +1,197 @@ +""" +通讯录子图API调用工具 +Contact Subgraph API Client +""" + +from typing import Dict, Any, Optional, List +from datetime import datetime +from dataclasses import dataclass + +from .state import Contact, Email + + +# 模拟数据库 +MOCK_CONTACTS_DB = {} +MOCK_EMAILS_DB = [] + + +@dataclass +class ContactAPIClient: + """ + 通讯录API客户端 - 可扩展支持多种后端 + """ + + def list_contacts_mock(self, user_id: str = "default") -> List[Contact]: + """ + 模拟查询联系人列表 + """ + if user_id not in MOCK_CONTACTS_DB: + # 初始化一些示例数据 + MOCK_CONTACTS_DB[user_id] = [ + Contact( + id="1", + name="张三", + phone="13800138000", + email="zhangsan@example.com", + company="科技公司", + position="工程师", + created_at=datetime.now().isoformat() + ), + Contact( + id="2", + name="李四", + phone="13900139000", + email="lisi@example.com", + company="贸易公司", + position="经理", + created_at=datetime.now().isoformat() + ), + Contact( + id="3", + name="王五", + phone="13700137000", + email="wangwu@example.com", + company="咨询公司", + position="顾问", + created_at=datetime.now().isoformat() + ) + ] + + return MOCK_CONTACTS_DB[user_id] + + def extract_contact_info_mock(self, query: str) -> Optional[Dict[str, Any]]: + """ + 模拟从查询中提取联系人信息 + """ + # 简化的提取逻辑 + import re + + # 提取邮箱 + email_match = re.search(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}', query) + # 提取手机号 + phone_match = re.search(r'1[3-9]\d{9}', query) + # 提取姓名(简单匹配) + # 先看是否有"添加"等关键词 + if any(keyword in query for keyword in ["添加", "add"]): + # 尝试提取姓名 + name = "未知" + # 简单的逻辑:去掉关键词、去掉邮箱和电话后剩下的 + clean_query = query + if email_match: + clean_query = clean_query.replace(email_match.group(), "") + if phone_match: + clean_query = clean_query.replace(phone_match.group(), "") + clean_query = clean_query.replace("添加", "").replace("add", "").replace("联系人", "").strip() + if clean_query: + name = clean_query + + return { + "name": name, + "phone": phone_match.group() if phone_match else "", + "email": email_match.group() if email_match else "", + "created_at": datetime.now().isoformat() + } + + return None + + def save_contact_mock(self, user_id: str, contact: Contact) -> bool: + """ + 模拟保存联系人 + """ + if user_id not in MOCK_CONTACTS_DB: + MOCK_CONTACTS_DB[user_id] = [] + + if not contact.id: + contact.id = str(len(MOCK_CONTACTS_DB[user_id]) + 1) + + MOCK_CONTACTS_DB[user_id].append(contact) + return True + + def list_emails_mock(self) -> List[Email]: + """ + 模拟查询邮件列表 + """ + global MOCK_EMAILS_DB + + if not MOCK_EMAILS_DB: + # 初始化一些示例邮件 + MOCK_EMAILS_DB = [ + Email( + id="1", + subject="会议邀请:AI技术分享", + sender="admin@example.com", + recipients=["user@example.com"], + date=datetime.now().isoformat(), + body="你好,下周一将举办AI技术分享会,欢迎参加。" + ), + Email( + id="2", + subject="项目进度更新", + sender="manager@example.com", + recipients=["user@example.com"], + date=datetime.now().isoformat(), + body="项目进度良好,继续保持。" + ) + ] + + return MOCK_EMAILS_DB + + def generate_email_draft_mock(self, query: str) -> Dict[str, str]: + """ + 模拟生成邮件草稿 + """ + # 简单的模板生成 + return { + "subject": f"Re: {query}", + "recipient": "recipient@example.com", + "body": "你好,\n\n这是一封自动生成的邮件草稿。\n\n此致,\n你的助手" + } + + def send_email_mock(self, recipient: str, subject: str, body: str) -> Dict[str, Any]: + """ + 模拟发送邮件 + """ + # 记录到模拟数据库 + MOCK_EMAILS_DB.append( + Email( + id=str(len(MOCK_EMAILS_DB) + 1), + subject=subject, + sender="me@example.com", + recipients=[recipient], + date=datetime.now().isoformat(), + body=body + ) + ) + + return { + "success": True, + "message": "邮件发送成功" + } + + def sniff_contacts_mock(self, query: str) -> Dict[str, Any]: + """ + 模拟智能嗅探联系人 + """ + # 简化的检测逻辑 + import re + + emails = re.findall(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}', query) + phones = re.findall(r'1[3-9]\d{9}', query) + + contacts = [] + for i, email in enumerate(emails): + contacts.append({ + "name": f"联系人{i+1}", + "email": email, + "phone": phones[i] if i < len(phones) else "" + }) + + return { + "contacts": contacts, + "count": len(contacts), + "suggestion": "是否添加这些联系人?" + } + + +# 单例实例 +contact_api = ContactAPIClient() diff --git a/backend/app/agent_subgraphs/contact/nodes.py b/backend/app/agent_subgraphs/contact/nodes.py index 497f73f..6b42b52 100644 --- a/backend/app/agent_subgraphs/contact/nodes.py +++ b/backend/app/agent_subgraphs/contact/nodes.py @@ -1,12 +1,17 @@ """ -通讯录子图节点 -Contact Subgraph Nodes +通讯录子图节点 - 完善版(使用API客户端) +Contact Subgraph Nodes - Complete (with API Client) """ from typing import Dict, Any from datetime import datetime from .state import ContactState, ContactAction, Contact, Email +from .api_client import contact_api + + +# 模拟联系人数据库(临时存储) +CONTACT_DB = {} def parse_intent(state: ContactState) -> ContactState: @@ -19,14 +24,14 @@ def parse_intent(state: ContactState) -> ContactState: query_lower = state.user_query.lower() # 简单的关键词匹配(真实场景应该用LLM) - if any(keyword in query_lower for keyword in ["联系人", "contact", "list"]): + # 优先匹配:添加联系人 + if any(keyword in query_lower for keyword in ["添加", "add", "新建", "save"]): + state.action = ContactAction.CONTACT_ADD + + elif any(keyword in query_lower for keyword in ["联系人", "contact", "list"]): state.action = ContactAction.CONTACT_LIST state.action_params = {"query": state.user_query} - elif any(keyword in query_lower for keyword in ["添加", "add", "新建", "save"]): - state.action = ContactAction.CONTACT_ADD - # TODO: 提取联系人信息 - elif any(keyword in query_lower for keyword in ["邮件", "email", "inbox"]): state.action = ContactAction.EMAIL_LIST @@ -45,11 +50,9 @@ def list_contacts(state: ContactState) -> ContactState: """ state.current_phase = "listing_contacts" - # TODO: 从数据库查询 - # 暂时返回空列表 - state.contacts = [] + # 使用API客户端获取联系人 + state.contacts = contact_api.list_contacts_mock(state.user_id) state.success = True - state.final_result = "暂无联系人" return state @@ -60,9 +63,19 @@ def add_contact(state: ContactState) -> ContactState: """ state.current_phase = "adding_contact" - # TODO: 实现添加联系人逻辑 - state.success = True - state.final_result = "联系人添加成功(待实现)" + # 从查询中提取联系人信息(简化版) + query = state.user_query + contact_data = contact_api.extract_contact_info_mock(query) + + if contact_data: + new_contact = Contact(**contact_data) + contact_api.save_contact_mock(state.user_id, new_contact) + state.current_contact = new_contact + state.success = True + state.final_result = f"✅ 联系人 {new_contact.name} 添加成功" + else: + state.success = False + state.error_message = "无法从查询中提取联系人信息" return state @@ -73,10 +86,9 @@ def list_emails(state: ContactState) -> ContactState: """ state.current_phase = "listing_emails" - # TODO: 从IMAP查询 - state.emails = [] + # 使用API客户端获取邮件 + state.emails = contact_api.list_emails_mock() state.success = True - state.final_result = "暂无邮件" return state @@ -87,10 +99,12 @@ def generate_email_draft(state: ContactState) -> ContactState: """ state.current_phase = "generating_draft" - # TODO: 使用LLM生成邮件草稿 - state.draft_subject = "邮件主题" - state.draft_recipient = "recipient@example.com" - state.draft_body = "这是邮件内容..." + # 使用API客户端生成邮件草稿 + draft = contact_api.generate_email_draft_mock(state.user_query) + + state.draft_subject = draft.get("subject", "") + state.draft_recipient = draft.get("recipient", "") + state.draft_body = draft.get("body", "") # 进入人工审核状态 state.pending_review = True @@ -125,9 +139,19 @@ def send_email(state: ContactState) -> ContactState: """ state.current_phase = "sending_email" - # TODO: 使用SMTP发送邮件 - state.success = True - state.final_result = "邮件发送成功(待实现)" + # 使用API客户端发送邮件 + result = contact_api.send_email_mock( + state.draft_recipient, + state.draft_subject, + state.draft_body + ) + + if result.get("success"): + state.success = True + state.final_result = "✅ 邮件发送成功" + else: + state.success = False + state.error_message = "邮件发送失败" return state @@ -139,46 +163,129 @@ def sniff_contacts(state: ContactState) -> ContactState: """ state.current_phase = "sniffing" - # TODO: 实现智能嗅探 + # 使用API客户端智能嗅探 + sniffed = contact_api.sniff_contacts_mock(state.user_query) + + state.sniff_result = sniffed + if sniffed.get("contacts"): + state.sniffed_contacts = [ + Contact(**contact) for contact in sniffed.get("contacts") + ] + state.sniff_confirmation_pending = True + state.success = True - state.final_result = "智能嗅探完成(待实现)" return state def format_result(state: ContactState) -> ContactState: """ - 格式化结果节点 + 格式化结果节点 - 精美展示 """ state.current_phase = "formatting" # 根据不同action生成不同的格式化输出 if state.action == ContactAction.CONTACT_LIST: + result = [] + result.append("═══════════════════════════════════════════") + result.append("📇 联系人列表") + result.append("═══════════════════════════════════════════") + result.append("") + if state.contacts: - result = "联系人列表:\n" for i, contact in enumerate(state.contacts, 1): - result += f"{i}. {contact.name}" + result.append(f"{i}. {contact.name}") if contact.phone: - result += f" - {contact.phone}" + result.append(f" 📞 电话:{contact.phone}") if contact.email: - result += f" ({contact.email})" - result += "\n" + result.append(f" 📧 邮箱:{contact.email}") + if contact.company: + result.append(f" 🏢 公司:{contact.company}") + result.append("") else: - result = "暂无联系人" - state.final_result = result + result.append(" 暂无联系人") + result.append("") + + result.append("═══════════════════════════════════════════") + result.append("💡 提示:添加联系人 张三 13800138000 zhangsan@example.com") + + state.final_result = "\n".join(result) + + elif state.action == ContactAction.CONTACT_ADD: + if state.final_result: + state.final_result = state.final_result + else: + result = [] + result.append("═══════════════════════════════════════════") + result.append("📇 添加联系人") + result.append("═══════════════════════════════════════════") + result.append("") + result.append(" ✅ 联系人添加成功") + result.append("") + result.append("═══════════════════════════════════════════") + state.final_result = "\n".join(result) elif state.action == ContactAction.EMAIL_LIST: + result = [] + result.append("═══════════════════════════════════════════") + result.append("📧 邮件列表") + result.append("═══════════════════════════════════════════") + result.append("") + if state.emails: - result = "邮件列表:\n" for i, email in enumerate(state.emails[:10], 1): - result += f"{i}. {email.subject} - {email.sender}\n" + result.append(f"{i}. {email.subject}") + result.append(f" 👤 发件人:{email.sender}") + if email.date: + result.append(f" 📅 日期:{email.date}") + result.append("") else: - result = "暂无邮件" - state.final_result = result + result.append(" 暂无邮件") + result.append("") + + result.append("═══════════════════════════════════════════") + state.final_result = "\n".join(result) + + elif state.action == ContactAction.EMAIL_SEND and state.pending_review: + result = [] + result.append("═══════════════════════════════════════════") + result.append("📧 邮件草稿(待审核)") + result.append("═══════════════════════════════════════════") + result.append("") + result.append(f"📌 主题:{state.draft_subject}") + result.append(f"👤 收件人:{state.draft_recipient}") + result.append("") + result.append("📝 内容:") + result.append(state.draft_body) + result.append("") + result.append("═══════════════════════════════════════════") + result.append("💡 提示:确认发送/取消/修改内容") + + state.final_result = "\n".join(result) + + elif state.sniff_result and state.sniffed_contacts: + result = [] + result.append("═══════════════════════════════════════════") + result.append("🕵️ 智能嗅探结果") + result.append("═══════════════════════════════════════════") + result.append("") + result.append(f" 发现 {len(state.sniffed_contacts)} 个可能的联系人:") + result.append("") + + for i, contact in enumerate(state.sniffed_contacts, 1): + result.append(f"{i}. {contact.name}") + if contact.phone: + result.append(f" 📞 电话:{contact.phone}") + if contact.email: + result.append(f" 📧 邮箱:{contact.email}") + result.append("") + + result.append("═══════════════════════════════════════════") + state.final_result = "\n".join(result) else: if not state.final_result: - state.final_result = "操作完成" + state.final_result = "通讯录操作完成" state.current_phase = "done" return state @@ -189,11 +296,11 @@ def should_continue(state: ContactState) -> str: 条件路由:决定下一步该做什么 """ if state.error_message: - return "finalize" + return "format_result" # 如果在审核中,等待 if state.pending_review: - return "human_review" + return "format_result" # 根据action路由 if state.action == ContactAction.NONE: @@ -206,8 +313,8 @@ def should_continue(state: ContactState) -> str: return "list_emails" elif state.action == ContactAction.EMAIL_SEND: if state.pending_review: - return "human_review" - elif state.draft_subject: + return "format_result" + elif state.draft_subject and not state.pending_review: return "send_email" else: return "generate_email_draft"