923 lines
38 KiB
Python
923 lines
38 KiB
Python
import logging
|
||
import json
|
||
import time
|
||
from datetime import datetime, timedelta
|
||
import uuid
|
||
from django.db import transaction
|
||
from django.db.models import Q
|
||
from django.utils import timezone
|
||
from apps.gmail.models import GmailConversation, GmailCredential, UserGoal, AutoReplyConfig
|
||
from apps.chat.models import ChatHistory
|
||
from apps.gmail.services.gmail_service import GmailService
|
||
from apps.gmail.services.goal_service import get_conversation_summary, get_last_message, generate_recommended_reply
|
||
from apps.common.services.ai_service import AIService
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
class AutoGmailConversationService:
|
||
"""
|
||
自动化Gmail对话服务,基于用户目标自动与多个达人进行沟通
|
||
"""
|
||
|
||
@staticmethod
|
||
def create_auto_conversation(user, user_email, influencer_email, greeting_message, goal_description):
|
||
"""
|
||
创建自动对话并发送初始消息
|
||
|
||
Args:
|
||
user: 用户对象
|
||
user_email: 用户邮箱(已授权)
|
||
influencer_email: 达人邮箱
|
||
greeting_message: 初始打招呼消息
|
||
goal_description: 对话目标
|
||
|
||
Returns:
|
||
tuple: (是否成功, 对话ID或错误信息, 目标对象或None)
|
||
"""
|
||
try:
|
||
# 验证用户Gmail凭证
|
||
credential = GmailCredential.objects.filter(user=user, email=user_email).first()
|
||
if not credential:
|
||
return False, f"未找到{user_email}的Gmail授权", None
|
||
|
||
# 检查是否已存在与该达人的对话
|
||
existing_conversation = GmailConversation.objects.filter(
|
||
user=user,
|
||
user_email=user_email,
|
||
influencer_email=influencer_email,
|
||
is_active=True
|
||
).first()
|
||
|
||
conversation = None
|
||
if existing_conversation:
|
||
conversation = existing_conversation
|
||
logger.info(f"找到与达人 {influencer_email} 的现有对话: {conversation.conversation_id}")
|
||
# 打印对话的has_sent_greeting字段值,以便追踪
|
||
logger.info(f"对话 {conversation.conversation_id} 的has_sent_greeting值: {conversation.has_sent_greeting}")
|
||
else:
|
||
# 创建新的对话
|
||
conversation_id = f"gmail_{user.id}_{str(uuid.uuid4())[:8]}"
|
||
conversation = GmailConversation.objects.create(
|
||
user=user,
|
||
user_email=user_email,
|
||
influencer_email=influencer_email,
|
||
conversation_id=conversation_id,
|
||
title=f"与 {influencer_email} 的Gmail对话",
|
||
is_active=True,
|
||
has_sent_greeting=False, # 新创建的对话尚未发送打招呼消息
|
||
metadata={
|
||
'auto_conversation': True,
|
||
'created_at': timezone.now().isoformat()
|
||
}
|
||
)
|
||
logger.info(f"创建新的自动对话: {conversation.conversation_id}, has_sent_greeting={conversation.has_sent_greeting}")
|
||
|
||
# 只有当对话尚未发送打招呼消息时才发送
|
||
logger.info(f"检查是否需要发送打招呼消息: conversation_id={conversation.conversation_id}, has_sent_greeting={conversation.has_sent_greeting}")
|
||
if not conversation.has_sent_greeting:
|
||
logger.info(f"对话 {conversation.conversation_id} 尚未发送打招呼消息,准备发送")
|
||
# 设置固定的问候消息
|
||
greeting_message = """Paid Collaboration Opportunity with TikTok's #1 Fragrance Brand 🌸
|
||
Hi,
|
||
I'm Vira from OOIN Media, and I'm reaching out on behalf of a top-performing fragrance brand Sttes on TikTok Shop—currently ranked #1 in the perfume category.
|
||
This brand has already launched several viral products and is now looking to partner with select creators like you through paid collaborations to continue driving awareness and sales.
|
||
We'd love to explore a partnership and would appreciate it if you could share:
|
||
Your rate for a single TikTok video
|
||
Whether you offer bundle pricing for multiple videos
|
||
Any additional details or formats you offer (e.g. story integration, livestream add-ons, etc.)
|
||
The product has strong market traction, proven conversions, and a competitive commission structure if you're also open to affiliate partnerships.
|
||
Looking forward to the opportunity to work together and hearing your rates!
|
||
Warm regards,
|
||
Vira
|
||
OOIN Media"""
|
||
|
||
# 发送打招呼消息
|
||
subject = "Paid Collaboration Opportunity with TikTok's #1 Fragrance Brand"
|
||
logger.info(f"开始向 {influencer_email} 发送打招呼消息")
|
||
success, message_id = GmailService.send_email(
|
||
user=user,
|
||
user_email=user_email,
|
||
to_email=influencer_email,
|
||
subject=subject,
|
||
body=greeting_message
|
||
)
|
||
|
||
if not success:
|
||
logger.error(f"发送打招呼消息失败: {message_id}")
|
||
return False, f"发送打招呼消息失败: {message_id}", None
|
||
|
||
# 更新对话的has_sent_greeting字段
|
||
conversation.has_sent_greeting = True
|
||
conversation.save(update_fields=['has_sent_greeting', 'updated_at'])
|
||
logger.info(f"对话 {conversation.conversation_id} 已发送打招呼消息,并更新了has_sent_greeting={conversation.has_sent_greeting}")
|
||
else:
|
||
logger.info(f"对话 {conversation.conversation_id} 已经发送过打招呼消息,不再重复发送")
|
||
|
||
# 创建目标
|
||
goal, error = AutoGmailConversationService.create_user_goal(
|
||
user=user,
|
||
conversation_id=conversation.conversation_id,
|
||
goal_description=goal_description
|
||
)
|
||
|
||
if error:
|
||
return False, f"创建对话目标失败: {error}", None
|
||
|
||
# 设置或刷新Gmail推送通知
|
||
notification_result, notification_error = GmailService.setup_gmail_push_notification(
|
||
user=user,
|
||
user_email=user_email
|
||
)
|
||
|
||
if not notification_result and notification_error:
|
||
logger.warning(f"设置Gmail推送通知失败: {notification_error},但对话创建成功")
|
||
|
||
# 查询最新的conversation对象以验证字段值
|
||
refreshed_conversation = GmailConversation.objects.get(id=conversation.id)
|
||
logger.info(f"返回结果前检查对话状态: conversation_id={refreshed_conversation.conversation_id}, has_sent_greeting={refreshed_conversation.has_sent_greeting}")
|
||
|
||
return True, conversation.conversation_id, goal
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建自动对话失败: {str(e)}")
|
||
import traceback
|
||
logger.error(traceback.format_exc())
|
||
return False, f"创建自动对话失败: {str(e)}", None
|
||
|
||
@staticmethod
|
||
def create_user_goal(user, conversation_id, goal_description):
|
||
"""
|
||
为用户创建对话目标
|
||
|
||
Args:
|
||
user: 用户对象
|
||
conversation_id: 对话ID
|
||
goal_description: 目标描述
|
||
|
||
Returns:
|
||
tuple: (目标对象, 错误信息)
|
||
"""
|
||
try:
|
||
# 检查对话是否存在
|
||
conversation = GmailConversation.objects.filter(conversation_id=conversation_id).first()
|
||
if not conversation:
|
||
return None, "对话不存在"
|
||
|
||
# 检查权限
|
||
if conversation.user.id != user.id:
|
||
return None, "无权限访问此对话"
|
||
|
||
# 停用该用户针对这个对话的之前的目标
|
||
UserGoal.objects.filter(
|
||
user=user,
|
||
conversation=conversation,
|
||
is_active=True
|
||
).update(is_active=False)
|
||
|
||
# 创建新目标
|
||
goal = UserGoal.objects.create(
|
||
user=user,
|
||
conversation=conversation,
|
||
description=goal_description,
|
||
is_active=True,
|
||
status='pending',
|
||
metadata={
|
||
'created_at': timezone.now().isoformat(),
|
||
'influencer_email': conversation.influencer_email,
|
||
'user_email': conversation.user_email
|
||
}
|
||
)
|
||
|
||
logger.info(f"用户 {user.username} 为对话 {conversation_id} 创建了新目标")
|
||
return goal, None
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建用户目标失败: {str(e)}")
|
||
return None, f"创建用户目标失败: {str(e)}"
|
||
|
||
@staticmethod
|
||
def check_goal_achieved(goal_description, conversation_history):
|
||
"""
|
||
检查目标是否已经达成
|
||
|
||
Args:
|
||
goal_description: 目标描述
|
||
conversation_history: 对话历史
|
||
|
||
Returns:
|
||
tuple: (是否达成, 置信度, 错误信息)
|
||
"""
|
||
try:
|
||
# 格式化对话历史
|
||
dialog_text = "\n\n".join([
|
||
f"{'用户' if msg.get('role') == 'user' else '达人'}: {msg.get('content', '')}"
|
||
for msg in conversation_history
|
||
])
|
||
|
||
# 构建提示词
|
||
prompt = f"""
|
||
分析以下对话历史,判断用户的目标是否已经达成:
|
||
|
||
用户目标: {goal_description}
|
||
|
||
对话历史:
|
||
{dialog_text}
|
||
|
||
请分析并判断此目标是否已经达成。仅回答"是"或"否",以及一个0到1之间的数字表示达成目标的置信度,格式为: "判断结果|置信度"。
|
||
例如: "是|0.85" 或 "否|0.32"
|
||
"""
|
||
|
||
messages = [
|
||
{
|
||
"role": "system",
|
||
"content": "你是一个专业的目标达成分析师,你的任务是判断对话中用户的目标是否已经达成。"
|
||
},
|
||
{
|
||
"role": "user",
|
||
"content": prompt
|
||
}
|
||
]
|
||
|
||
# 调用AI服务
|
||
result, error = AIService.call_silicon_cloud_api(
|
||
messages,
|
||
model="Pro/deepseek-ai/DeepSeek-R1",
|
||
max_tokens=100,
|
||
temperature=0.1
|
||
)
|
||
|
||
if error:
|
||
return False, 0, error
|
||
|
||
# 解析结果
|
||
result = result.strip().lower()
|
||
|
||
# 尝试提取判断结果和置信度
|
||
if '|' in result:
|
||
judgment, confidence_str = result.split('|', 1)
|
||
is_achieved = judgment.strip() == '是'
|
||
|
||
try:
|
||
confidence = float(confidence_str.strip())
|
||
confidence = max(0, min(1, confidence)) # 确保在0-1之间
|
||
except ValueError:
|
||
confidence = 0.5 # 默认置信度
|
||
|
||
return is_achieved, confidence, None
|
||
else:
|
||
# 兜底处理
|
||
is_achieved = '是' in result
|
||
return is_achieved, 0.5, "AI返回格式异常"
|
||
|
||
except Exception as e:
|
||
logger.error(f"检查目标达成状态失败: {str(e)}")
|
||
return False, 0, f"检查目标达成状态失败: {str(e)}"
|
||
|
||
@staticmethod
|
||
def generate_reply_and_send(user, goal_id, conversation_id=None):
|
||
"""
|
||
生成回复并发送邮件
|
||
|
||
Args:
|
||
user: 用户对象
|
||
goal_id: 目标ID
|
||
conversation_id: 对话ID (可选,如果提供则验证目标与对话的关联)
|
||
|
||
Returns:
|
||
tuple: (成功标志, 错误信息)
|
||
"""
|
||
try:
|
||
# 获取目标信息
|
||
goal = UserGoal.objects.filter(id=goal_id, user=user).first()
|
||
if not goal:
|
||
return False, "目标不存在或不属于当前用户"
|
||
|
||
# 获取关联的对话ID
|
||
goal_conversation_id = goal.get_conversation_id()
|
||
|
||
# 如果提供了conversation_id,验证与目标关联的对话一致
|
||
if conversation_id and goal_conversation_id and conversation_id != goal_conversation_id:
|
||
return False, "目标与对话不匹配"
|
||
|
||
# 确定最终使用的对话ID
|
||
final_conversation_id = conversation_id or goal_conversation_id
|
||
if not final_conversation_id:
|
||
return False, "无法确定对话ID,目标未关联对话"
|
||
|
||
# 获取对话信息
|
||
conversation = GmailConversation.objects.filter(conversation_id=final_conversation_id).first()
|
||
if not conversation:
|
||
return False, "对话不存在"
|
||
|
||
# 检查权限
|
||
if conversation.user.id != user.id:
|
||
return False, "无权限访问此对话"
|
||
|
||
# 获取Gmail凭证
|
||
credential = GmailCredential.objects.filter(user=user, email=conversation.user_email).first()
|
||
if not credential:
|
||
return False, f"未找到{conversation.user_email}的Gmail凭证"
|
||
|
||
# 获取对话摘要
|
||
conversation_summary = get_conversation_summary(final_conversation_id)
|
||
if not conversation_summary:
|
||
conversation_summary = "无对话摘要"
|
||
|
||
# 获取最后一条达人消息
|
||
last_message = get_last_message(final_conversation_id)
|
||
if not last_message:
|
||
return False, "对话中没有达人消息,无法生成回复"
|
||
|
||
# 生成回复内容
|
||
reply_content, error = generate_recommended_reply(
|
||
user=user,
|
||
goal_description=goal.description,
|
||
conversation_summary=conversation_summary,
|
||
last_message=last_message
|
||
)
|
||
|
||
if error:
|
||
return False, f"生成回复内容失败: {error}"
|
||
|
||
if not reply_content:
|
||
return False, "生成的回复内容为空"
|
||
|
||
# 从最后一条达人消息中提取主题
|
||
subject = "回复: "
|
||
if last_message and "主题:" in last_message:
|
||
subject_line = last_message.split("\n")[0]
|
||
if "主题:" in subject_line:
|
||
original_subject = subject_line.split("主题:", 1)[1].strip()
|
||
subject = f"回复: {original_subject}"
|
||
|
||
# 发送邮件
|
||
success, message_id = GmailService.send_email(
|
||
user=user,
|
||
user_email=conversation.user_email,
|
||
to_email=conversation.influencer_email,
|
||
subject=subject,
|
||
body=reply_content
|
||
)
|
||
|
||
if not success:
|
||
return False, f"发送邮件失败: {message_id}"
|
||
|
||
# 更新目标状态
|
||
goal.last_activity_time = timezone.now()
|
||
goal.status = 'in_progress'
|
||
goal.metadata = goal.metadata or {}
|
||
goal.metadata['last_reply_time'] = timezone.now().isoformat()
|
||
goal.metadata['last_reply_id'] = message_id
|
||
goal.save()
|
||
|
||
logger.info(f"成功为用户 {user.username} 生成并发送回复邮件")
|
||
return True, message_id
|
||
|
||
except Exception as e:
|
||
logger.error(f"生成回复并发送邮件失败: {str(e)}")
|
||
return False, f"生成回复并发送邮件失败: {str(e)}"
|
||
|
||
@staticmethod
|
||
def get_active_goals_for_conversation(user, conversation_id):
|
||
"""
|
||
获取指定对话的活跃目标
|
||
|
||
Args:
|
||
user: 用户对象
|
||
conversation_id: 对话ID
|
||
|
||
Returns:
|
||
UserGoal: 活跃目标对象或None
|
||
"""
|
||
# 先查找对话对象
|
||
conversation = GmailConversation.objects.filter(conversation_id=conversation_id).first()
|
||
if not conversation:
|
||
return None
|
||
|
||
# 查找与对话关联的活跃目标
|
||
return UserGoal.objects.filter(
|
||
user=user,
|
||
conversation=conversation,
|
||
is_active=True
|
||
).order_by('-created_at').first()
|
||
|
||
@staticmethod
|
||
def get_recommended_reply(user, conversation_id):
|
||
"""
|
||
获取推荐回复
|
||
|
||
Args:
|
||
user: 用户对象
|
||
conversation_id: 对话ID
|
||
|
||
Returns:
|
||
tuple: (推荐回复内容, 错误信息)
|
||
"""
|
||
try:
|
||
# 获取对话信息
|
||
conversation = GmailConversation.objects.filter(conversation_id=conversation_id).first()
|
||
if not conversation:
|
||
return None, "对话不存在"
|
||
|
||
# 检查权限
|
||
if conversation.user.id != user.id:
|
||
return None, "无权限访问此对话"
|
||
|
||
# 获取该对话的目标
|
||
goal = AutoGmailConversationService.get_active_goals_for_conversation(user, conversation_id)
|
||
if not goal:
|
||
return None, "未找到该对话的活跃目标"
|
||
|
||
# 获取对话摘要
|
||
conversation_summary = get_conversation_summary(conversation_id)
|
||
if not conversation_summary:
|
||
conversation_summary = "无对话摘要"
|
||
|
||
# 获取最后一条达人消息
|
||
last_message = get_last_message(conversation_id)
|
||
if not last_message:
|
||
return None, "对话中没有达人消息,无法生成推荐回复"
|
||
|
||
# 生成推荐回复
|
||
reply_content, error = generate_recommended_reply(
|
||
user=user,
|
||
goal_description=goal.description,
|
||
conversation_summary=conversation_summary,
|
||
last_message=last_message
|
||
)
|
||
|
||
if error:
|
||
return None, f"生成推荐回复失败: {error}"
|
||
|
||
return reply_content, None
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取推荐回复失败: {str(e)}")
|
||
return None, f"获取推荐回复失败: {str(e)}"
|
||
|
||
@staticmethod
|
||
@transaction.atomic
|
||
def process_active_goals():
|
||
"""
|
||
处理所有活跃的目标,检查新消息并自动回复
|
||
"""
|
||
try:
|
||
# 获取所有活跃目标
|
||
active_goals = UserGoal.objects.filter(
|
||
is_active=True,
|
||
status__in=['pending', 'in_progress']
|
||
).select_related('user', 'conversation')
|
||
|
||
logger.info(f"发现 {active_goals.count()} 个活跃目标需要处理")
|
||
|
||
for goal in active_goals:
|
||
try:
|
||
# 获取对话ID
|
||
if not goal.conversation:
|
||
logger.error(f"目标 {goal.id} 没有关联对话")
|
||
continue
|
||
|
||
conversation_id = goal.conversation.conversation_id
|
||
|
||
# 获取用户
|
||
user = goal.user
|
||
|
||
# 获取最近的消息记录 - 修改查询方式避免"Cannot filter a query once a slice has been taken"错误
|
||
# 分别获取最新的用户消息和助手消息
|
||
latest_assistant_msg = ChatHistory.objects.filter(
|
||
conversation_id=conversation_id,
|
||
role='assistant'
|
||
).order_by('-created_at').first()
|
||
|
||
latest_user_msg = ChatHistory.objects.filter(
|
||
conversation_id=conversation_id,
|
||
role='user'
|
||
).order_by('-created_at').first()
|
||
|
||
# 获取用于分析目标达成情况的最近消息记录
|
||
recent_messages = ChatHistory.objects.filter(
|
||
conversation_id=conversation_id
|
||
).order_by('-created_at')[:20]
|
||
|
||
# 生成对话历史,用于判断目标是否达成
|
||
conversation_history = []
|
||
for msg in reversed(list(recent_messages)): # 按时间顺序排列
|
||
conversation_history.append({
|
||
'role': msg.role,
|
||
'content': msg.content,
|
||
'created_at': msg.created_at.isoformat()
|
||
})
|
||
|
||
# 检查目标是否已达成
|
||
is_achieved, confidence, error = AutoGmailConversationService.check_goal_achieved(
|
||
goal_description=goal.description,
|
||
conversation_history=conversation_history
|
||
)
|
||
|
||
# 更新目标状态
|
||
goal.metadata = goal.metadata or {}
|
||
goal.metadata['last_check_time'] = timezone.now().isoformat()
|
||
|
||
if is_achieved and confidence >= 0.7: # 高置信度认为目标已达成
|
||
goal.status = 'completed'
|
||
goal.completion_time = timezone.now()
|
||
goal.metadata['completion_confidence'] = confidence
|
||
goal.save()
|
||
logger.info(f"目标 {goal.id} 已达成,置信度: {confidence}")
|
||
continue
|
||
|
||
# 获取最近的一条消息,不区分角色
|
||
latest_msg = ChatHistory.objects.filter(
|
||
conversation_id=conversation_id
|
||
).order_by('-created_at').first()
|
||
|
||
# 检查是否有新的达人消息需要回复
|
||
# 输出详细的消息角色和ID信息以便调试
|
||
if latest_assistant_msg:
|
||
logger.info(f"最新达人消息: ID={latest_assistant_msg.id}, 时间={latest_assistant_msg.created_at}, 内容前20字符={latest_assistant_msg.content[:20]}")
|
||
if latest_user_msg:
|
||
logger.info(f"最新用户消息: ID={latest_user_msg.id}, 时间={latest_user_msg.created_at}, 内容前20字符={latest_user_msg.content[:20]}")
|
||
if latest_msg:
|
||
logger.info(f"最新消息(任意角色): ID={latest_msg.id}, 角色={latest_msg.role}, 时间={latest_msg.created_at}")
|
||
|
||
# 如果最后一条消息是达人发的,则需要回复
|
||
needs_reply = latest_msg and latest_msg.role == 'assistant'
|
||
|
||
if not needs_reply:
|
||
# 更新目标状态
|
||
goal.status = 'in_progress'
|
||
goal.save()
|
||
logger.info(f"目标 {goal.id} 无需回复,最后消息角色为: {latest_msg.role if latest_msg else '无消息'}")
|
||
continue
|
||
|
||
# 需要回复,直接生成回复并发送
|
||
logger.info(f"目标 {goal.id} 需要回复,最后消息来自达人")
|
||
success, msg = AutoGmailConversationService.generate_reply_and_send(
|
||
user=user,
|
||
goal_id=goal.id,
|
||
conversation_id=conversation_id
|
||
)
|
||
|
||
if success:
|
||
# 更新目标状态
|
||
goal.status = 'in_progress'
|
||
goal.save()
|
||
logger.info(f"目标 {goal.id} 自动回复成功")
|
||
else:
|
||
logger.error(f"目标 {goal.id} 自动回复失败: {msg}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"处理目标 {goal.id} 时出错: {str(e)}")
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"处理活跃目标失败: {str(e)}")
|
||
return False
|
||
|
||
@staticmethod
|
||
def get_all_user_conversations_with_goals(user):
|
||
"""
|
||
获取用户所有对话和对应的目标
|
||
|
||
Args:
|
||
user: 用户对象
|
||
|
||
Returns:
|
||
list: 对话和目标信息列表
|
||
"""
|
||
try:
|
||
# 获取用户所有对话
|
||
conversations = GmailConversation.objects.filter(user=user).order_by('-updated_at')
|
||
|
||
result = []
|
||
for conversation in conversations:
|
||
# 查询该对话的活跃目标
|
||
goal = UserGoal.objects.filter(
|
||
user=user,
|
||
conversation=conversation,
|
||
is_active=True
|
||
).order_by('-created_at').first()
|
||
|
||
# 准备对话和目标信息
|
||
conv_data = {
|
||
'conversation_id': conversation.conversation_id,
|
||
'influencer_email': conversation.influencer_email,
|
||
'user_email': conversation.user_email,
|
||
'title': conversation.title,
|
||
'last_message_time': conversation.updated_at,
|
||
'has_active_goal': goal is not None
|
||
}
|
||
|
||
# 如果有活跃目标,添加目标信息
|
||
if goal:
|
||
conv_data['goal'] = {
|
||
'id': str(goal.id),
|
||
'description': goal.description,
|
||
'status': goal.status,
|
||
'created_at': goal.created_at,
|
||
'updated_at': goal.updated_at
|
||
}
|
||
|
||
result.append(conv_data)
|
||
|
||
return result
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取用户对话和目标失败: {str(e)}")
|
||
return []
|
||
|
||
@staticmethod
|
||
def find_auto_reply_config(user_email, influencer_email):
|
||
"""
|
||
查找匹配的自动回复配置
|
||
|
||
Args:
|
||
user_email: 用户Gmail邮箱
|
||
influencer_email: 达人Gmail邮箱
|
||
|
||
Returns:
|
||
AutoReplyConfig: 匹配的自动回复配置或None
|
||
"""
|
||
try:
|
||
config = AutoReplyConfig.objects.filter(
|
||
user_email=user_email,
|
||
influencer_email=influencer_email,
|
||
is_enabled=True
|
||
).first()
|
||
|
||
return config
|
||
except Exception as e:
|
||
logger.error(f"查找自动回复配置失败: {str(e)}")
|
||
return None
|
||
|
||
@staticmethod
|
||
def get_or_create_conversation(user, user_email, influencer_email):
|
||
"""
|
||
获取或创建Gmail对话
|
||
|
||
Args:
|
||
user: 用户对象
|
||
user_email: 用户Gmail邮箱
|
||
influencer_email: 达人Gmail邮箱
|
||
|
||
Returns:
|
||
tuple: (GmailConversation, bool) - 对话对象和是否新创建
|
||
"""
|
||
try:
|
||
# 查找现有对话
|
||
conversation = GmailConversation.objects.filter(
|
||
user=user,
|
||
user_email=user_email,
|
||
influencer_email=influencer_email,
|
||
is_active=True
|
||
).first()
|
||
|
||
if conversation:
|
||
return conversation, False
|
||
|
||
# 创建新对话
|
||
conversation_id = f"feishu_gmail_{user.id}_{influencer_email.split('@')[0]}_{timezone.now().strftime('%m%d%H%M')}"
|
||
conversation = GmailConversation.objects.create(
|
||
user=user,
|
||
user_email=user_email,
|
||
influencer_email=influencer_email,
|
||
conversation_id=conversation_id,
|
||
title=f"飞书与 {influencer_email} 的自动对话",
|
||
is_active=True,
|
||
metadata={
|
||
'auto_reply': True,
|
||
'feishu_auto': True,
|
||
'created_at': timezone.now().isoformat()
|
||
}
|
||
)
|
||
|
||
return conversation, True
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取或创建Gmail对话失败: {str(e)}")
|
||
return None, False
|
||
|
||
@staticmethod
|
||
def should_auto_reply(config, message_data):
|
||
"""
|
||
判断是否应该自动回复
|
||
|
||
Args:
|
||
config: 自动回复配置
|
||
message_data: 消息数据,包含发件人、收件人等信息
|
||
|
||
Returns:
|
||
bool: 是否应该自动回复
|
||
"""
|
||
if not config or not config.can_reply():
|
||
return False
|
||
|
||
# 判断消息发送时间,避免回复太老的消息
|
||
message_time = message_data.get('timestamp')
|
||
if message_time:
|
||
try:
|
||
message_time = timezone.datetime.fromisoformat(message_time.replace('Z', '+00:00'))
|
||
current_time = timezone.now()
|
||
# 只回复24小时内的消息
|
||
if (current_time - message_time) > timedelta(hours=24):
|
||
logger.info(f"消息太旧,不自动回复: {message_time}")
|
||
return False
|
||
except Exception as e:
|
||
logger.warning(f"解析消息时间失败: {str(e)}")
|
||
|
||
# 检查上次回复时间,避免频繁回复
|
||
if config.last_reply_time:
|
||
time_since_last_reply = timezone.now() - config.last_reply_time
|
||
# 至少间隔30分钟
|
||
if time_since_last_reply < timedelta(minutes=30):
|
||
logger.info(f"距离上次回复时间太短,不自动回复: {time_since_last_reply}")
|
||
return False
|
||
|
||
return True
|
||
|
||
|
||
@staticmethod
|
||
def process_webhook_message(message_data):
|
||
"""
|
||
处理来自webhook的消息推送,自动回复活跃对话
|
||
|
||
Args:
|
||
message_data: 邮件数据
|
||
|
||
Returns:
|
||
bool: 是否成功处理
|
||
"""
|
||
try:
|
||
# 提取发件人邮箱
|
||
from_email = message_data.get('from', {}).get('emailAddress', {}).get('address')
|
||
if not from_email:
|
||
logger.warning(f"无法提取发件人邮箱: {message_data}")
|
||
return False
|
||
|
||
# 提取收件人邮箱
|
||
to_emails = []
|
||
for recipient in message_data.get('toRecipients', []):
|
||
email = recipient.get('emailAddress', {}).get('address')
|
||
if email:
|
||
to_emails.append(email)
|
||
|
||
if not to_emails:
|
||
logger.warning(f"无法提取收件人邮箱: {message_data}")
|
||
return False
|
||
|
||
# 收件人邮箱是我们系统用户的邮箱,发件人是达人邮箱
|
||
user_email = to_emails[0] # 假设第一个收件人是我们系统的用户
|
||
influencer_email = from_email
|
||
|
||
logger.info(f"处理webhook消息: 发件人(达人)={influencer_email}, 收件人(用户)={user_email}")
|
||
|
||
# 查找符合条件的活跃对话
|
||
active_conversations = GmailConversation.objects.filter(
|
||
user_email=user_email,
|
||
influencer_email=influencer_email,
|
||
is_active=True
|
||
).select_related('user')
|
||
|
||
if not active_conversations.exists():
|
||
logger.info(f"未找到与达人 {influencer_email} 的活跃对话")
|
||
return False
|
||
|
||
# 获取最近的一个活跃对话
|
||
conversation = active_conversations.order_by('-updated_at').first()
|
||
user = conversation.user
|
||
|
||
logger.info(f"找到活跃对话: {conversation.conversation_id}, 用户: {user.username}")
|
||
|
||
# 验证Gmail凭证
|
||
credential = GmailCredential.objects.filter(user=user, email=user_email).first()
|
||
if not credential:
|
||
logger.error(f"未找到用户 {user.username} 的Gmail凭证: {user_email}")
|
||
return False
|
||
|
||
# 记录邮件内容到对话历史
|
||
message_id = message_data.get('id')
|
||
subject = message_data.get('subject', '无主题')
|
||
body = message_data.get('body', {}).get('content', '')
|
||
|
||
# 创建达人消息记录
|
||
chat_message = ChatHistory.objects.create(
|
||
conversation_id=conversation.conversation_id,
|
||
message_id=f"email_{message_id}",
|
||
role='assistant', # 达人消息用assistant角色
|
||
content=f"主题: {subject}\n\n{body}",
|
||
metadata={
|
||
'email_id': message_id,
|
||
'from': influencer_email,
|
||
'to': user_email,
|
||
'subject': subject,
|
||
'timestamp': message_data.get('timestamp'),
|
||
'webhook_auto': True
|
||
}
|
||
)
|
||
|
||
# 获取活跃目标
|
||
goal = AutoGmailConversationService.get_active_goals_for_conversation(user, conversation.conversation_id)
|
||
|
||
# 获取对话摘要
|
||
conversation_summary = get_conversation_summary(conversation.conversation_id)
|
||
if not conversation_summary:
|
||
conversation_summary = "无对话摘要"
|
||
|
||
# 获取最后一条消息
|
||
last_message = get_last_message(conversation.conversation_id)
|
||
if not last_message:
|
||
last_message = f"主题: {subject}\n\n{body}"
|
||
|
||
# 生成推荐回复
|
||
goal_description = goal.description if goal else "礼貌回应邮件并获取更多信息"
|
||
reply_content, error = generate_recommended_reply(
|
||
user=user,
|
||
goal_description=goal_description,
|
||
conversation_summary=conversation_summary,
|
||
last_message=last_message
|
||
)
|
||
|
||
if error:
|
||
logger.error(f"生成推荐回复失败: {error}")
|
||
return False
|
||
|
||
if not reply_content:
|
||
logger.error("生成的推荐回复内容为空")
|
||
return False
|
||
|
||
# 构建回复的主题
|
||
reply_subject = f"回复: {subject}" if not subject.startswith('回复:') else subject
|
||
|
||
# 发送回复邮件
|
||
success, reply_message_id = GmailService.send_email(
|
||
user=user,
|
||
user_email=user_email,
|
||
to_email=influencer_email,
|
||
subject=reply_subject,
|
||
body=reply_content
|
||
)
|
||
|
||
if not success:
|
||
logger.error(f"发送自动回复邮件失败: {reply_message_id}")
|
||
return False
|
||
|
||
# 记录自动回复到对话历史
|
||
ChatHistory.objects.create(
|
||
conversation_id=conversation.conversation_id,
|
||
message_id=f"email_reply_{reply_message_id}",
|
||
role='user', # 用户发出的消息用user角色
|
||
content=reply_content,
|
||
metadata={
|
||
'email_id': reply_message_id,
|
||
'from': user_email,
|
||
'to': influencer_email,
|
||
'subject': reply_subject,
|
||
'auto_reply': True,
|
||
'webhook_auto': True,
|
||
'timestamp': timezone.now().isoformat()
|
||
}
|
||
)
|
||
|
||
# 更新目标状态(如果有)
|
||
if goal:
|
||
goal.last_activity_time = timezone.now()
|
||
goal.status = 'in_progress'
|
||
goal.metadata = goal.metadata or {}
|
||
goal.metadata['last_reply_time'] = timezone.now().isoformat()
|
||
goal.metadata['last_reply_id'] = reply_message_id
|
||
goal.save()
|
||
|
||
# 检查目标是否达成
|
||
latest_messages = ChatHistory.objects.filter(
|
||
conversation_id=conversation.conversation_id
|
||
).order_by('-created_at')[:20]
|
||
|
||
conversation_history = []
|
||
for msg in reversed(list(latest_messages)):
|
||
conversation_history.append({
|
||
'role': msg.role,
|
||
'content': msg.content,
|
||
'created_at': msg.created_at.isoformat()
|
||
})
|
||
|
||
is_achieved, confidence, error = AutoGmailConversationService.check_goal_achieved(
|
||
goal_description=goal.description,
|
||
conversation_history=conversation_history
|
||
)
|
||
|
||
if is_achieved and confidence >= 0.7:
|
||
goal.status = 'completed'
|
||
goal.completion_time = timezone.now()
|
||
goal.metadata['completion_confidence'] = confidence
|
||
goal.save()
|
||
logger.info(f"目标 {goal.id} 已达成,置信度: {confidence}")
|
||
|
||
logger.info(f"成功回复webhook消息: {user_email} -> {influencer_email}")
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"处理webhook消息失败: {str(e)}")
|
||
import traceback
|
||
logger.error(traceback.format_exc())
|
||
return False |