2025-04-17 16:14:00 +08:00
import os
import sys
import json
import logging
import traceback
import requests
from datetime import datetime
import django
from django . db import transaction
from django . contrib . auth import get_user_model
import time
2025-04-29 10:22:57 +08:00
import pandas as pd
2025-04-17 16:14:00 +08:00
# 设置Django环境
sys . path . append ( os . path . dirname ( os . path . dirname ( os . path . abspath ( __file__ ) ) ) )
os . environ . setdefault ( ' DJANGO_SETTINGS_MODULE ' , ' role_based_system.settings ' )
django . setup ( )
import lark_oapi as lark
from lark_oapi . api . bitable . v1 import *
from user_management . models import (
FeishuCreator , KnowledgeBase , UserGoal ,
GmailTalentMapping , ChatHistory , ConversationSummary
)
from django . conf import settings
from user_management . gmail_integration import GmailIntegration
from feishu . feishu import sync_to_knowledge_base
logger = logging . getLogger ( __name__ )
def get_tenant_access_token ( app_id , app_secret ) :
"""
获取飞书应用的tenant_access_token
"""
url = " https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal "
headers = {
" Content-Type " : " application/json "
}
data = {
" app_id " : app_id ,
" app_secret " : app_secret
}
try :
response = requests . post ( url , json = data , headers = headers )
response_json = response . json ( )
if response . status_code == 200 and response_json . get ( " code " ) == 0 :
return {
" status " : " success " ,
" access_token " : response_json . get ( " tenant_access_token " ) ,
" expire " : response_json . get ( " expire " )
}
else :
logging . error ( f " 获取tenant_access_token失败: { response_json } " )
return {
" status " : " error " ,
" message " : response_json . get ( " msg " , " 未知错误 " )
}
except Exception as e :
logging . error ( f " 获取tenant_access_token异常: { str ( e ) } " )
return {
" status " : " error " ,
" message " : str ( e )
}
def fetch_table_records ( app_token , table_id , view_id , access_token = None , app_id = None , app_secret = None ) :
"""
从飞书多维表格获取记录
参数 :
app_token : 应用ID
table_id : 表格ID
view_id : 视图ID
access_token : 访问令牌 ( 可选 , 如果提供则直接使用 )
app_id : 应用ID ( 可选 , 用于自动获取token )
app_secret : 应用密钥 ( 可选 , 用于自动获取token )
"""
# 如果没有提供access_token但提供了app_id和app_secret, 则自动获取
if not access_token and app_id and app_secret :
token_result = get_tenant_access_token ( app_id , app_secret )
if token_result [ " status " ] == " success " :
access_token = token_result [ " access_token " ]
else :
logging . error ( f " 无法获取访问令牌: { token_result [ ' message ' ] } " )
return [ ]
total_records = [ ]
page_token = None
page_size = 20
try :
# 初始化客户端
client = lark . Client . builder ( ) \
. enable_set_token ( True ) \
. log_level ( lark . LogLevel . DEBUG ) \
. build ( )
logger . info ( f " 开始从飞书表格获取数据: app_token= { app_token } , table_id= { table_id } , view_id= { view_id } " )
# 尝试两种方法获取记录
# 方法1: 使用list接口
list_success = False
try :
while True :
try :
# 构造请求对象
builder = ListAppTableRecordRequest . builder ( ) \
. app_token ( app_token ) \
. table_id ( table_id ) \
. page_size ( page_size ) \
. view_id ( view_id )
# 如果有page_token, 添加到请求中
if page_token :
builder = builder . page_token ( page_token )
# 构建完整请求
request = builder . build ( )
logger . debug ( f " 发送list请求, page_token: { page_token } " )
# 发起请求
option = lark . RequestOption . builder ( ) . user_access_token ( access_token ) . build ( )
response = client . bitable . v1 . app_table_record . list ( request , option )
if not response . success ( ) :
logger . error ( f " list请求失败: { response . code } , { response . msg } " )
break
# 获取当前页记录
current_records = response . data . items
if not current_records :
logger . info ( " 没有更多记录 " )
break
total_records . extend ( current_records )
list_success = True
# 解析响应数据获取分页信息
response_data = json . loads ( response . raw . content )
has_more = response_data [ " data " ] . get ( " has_more " , False )
total = response_data [ " data " ] . get ( " total " , 0 )
logger . info ( f " list方法获取到 { len ( current_records ) } 条记录,当前总计: { len ( total_records ) } / { total } 条 " )
# 获取下一页token
page_token = response_data [ " data " ] . get ( " page_token " )
if not page_token or not has_more :
logger . info ( " list方法已获取所有数据 " )
break
except Exception as e :
logger . error ( f " list方法获取记录时出错: { str ( e ) } " )
logger . error ( traceback . format_exc ( ) )
list_success = False
break
except Exception as e :
logger . error ( f " list方法整体出错: { str ( e ) } " )
list_success = False
# 如果list方法失败, 尝试search方法
if not list_success or not total_records :
logger . info ( " 尝试使用search方法获取数据... " )
page_token = None
total_records = [ ]
while True :
try :
# 构造search请求对象
request = SearchAppTableRecordRequest . builder ( ) \
. app_token ( app_token ) \
. table_id ( table_id ) \
. page_size ( page_size )
# 如果有page_token, 添加到请求中
if page_token :
request = request . page_token ( page_token )
# 添加请求体 - 可以根据需要添加过滤条件
request = request . request_body (
SearchAppTableRecordRequestBody . builder ( ) . build ( )
) . build ( )
logger . debug ( f " 发送search请求, page_token: { page_token } " )
# 发起请求
option = lark . RequestOption . builder ( ) . user_access_token ( access_token ) . build ( )
response = client . bitable . v1 . app_table_record . search ( request , option )
if not response . success ( ) :
logger . error ( f " search请求失败: { response . code } , { response . msg } " )
break
# 获取当前页记录
current_records = response . data . items
if not current_records :
logger . info ( " search方法没有更多记录 " )
break
total_records . extend ( current_records )
# 解析响应数据获取分页信息
response_data = json . loads ( response . raw . content )
has_more = response_data [ " data " ] . get ( " has_more " , False )
total = response_data [ " data " ] . get ( " total " , 0 )
logger . info ( f " search方法获取到 { len ( current_records ) } 条记录,当前总计: { len ( total_records ) } / { total } 条 " )
# 获取下一页token
page_token = response_data [ " data " ] . get ( " page_token " )
if not page_token or not has_more :
logger . info ( " search方法已获取所有数据 " )
break
except Exception as e :
logger . error ( f " search方法获取记录时出错: { str ( e ) } " )
logger . error ( traceback . format_exc ( ) )
break
logger . info ( f " 最终获取到 { len ( total_records ) } 条记录 " )
return total_records
except Exception as e :
logger . error ( f " 获取飞书表格记录时出错: { str ( e ) } " )
logger . error ( traceback . format_exc ( ) )
return [ ]
def extract_field_value ( field_value ) :
"""
提取字段值
参数 :
field_value : 飞书返回的字段值
返回 :
任意类型 : 提取后的值
"""
if isinstance ( field_value , list ) :
if field_value and isinstance ( field_value [ 0 ] , dict ) :
return field_value [ 0 ] . get ( ' text ' , ' ' )
elif isinstance ( field_value , dict ) :
if ' text ' in field_value :
return field_value [ ' text ' ]
elif ' link ' in field_value :
return field_value [ ' link ' ]
elif ' link_record_ids ' in field_value :
return ' '
return field_value
def find_duplicate_email_creators ( records ) :
"""
2025-04-29 10:22:57 +08:00
查找记录中与数据库中FeishuCreator表匹配的邮箱
2025-04-17 16:14:00 +08:00
参数 :
records : 飞书记录列表
返回 :
dict : 以邮箱为键 , 记录列表为值的字典
"""
2025-04-29 10:22:57 +08:00
matching_emails = { }
2025-04-17 16:14:00 +08:00
for record in records :
fields = record . fields
email = extract_field_value ( fields . get ( ' 邮箱 ' , ' ' ) )
if email :
2025-04-29 10:22:57 +08:00
# 检查邮箱是否存在于FeishuCreator表中
creator = FeishuCreator . objects . filter ( email = email ) . first ( )
if creator :
if email not in matching_emails :
matching_emails [ email ] = [ ]
matching_emails [ email ] . append ( record )
logger . info ( f " 找到匹配的creator邮箱: { email } , creator_id: { creator . id } " )
logger . info ( f " 共发现 { len ( matching_emails ) } 个与FeishuCreator表匹配的邮箱 " )
return matching_emails
2025-04-17 16:14:00 +08:00
def create_or_update_knowledge_base ( email , user = None ) :
"""
为创作者创建或更新知识库
参数 :
email : 创作者邮箱
user : 用户对象 , 默认为None ( 将选择一个组长 )
返回 :
tuple : ( KnowledgeBase对象 , 是否新创建 )
"""
# 优先使用给定的用户,否则获取一个组长用户
if not user :
User = get_user_model ( )
user = User . objects . filter ( role = ' leader ' ) . first ( )
if not user :
logger . error ( " 未找到组长用户,无法创建知识库 " )
return None , False
# 首先查找创作者
creator = FeishuCreator . objects . filter ( email = email ) . first ( )
if not creator :
logger . error ( f " 找不到邮箱为 { email } 的创作者 " )
return None , False
# 使用sync_to_knowledge_base函数创建知识库
kb , created = sync_to_knowledge_base ( creator_id = creator . id )
if kb :
logger . info ( f " 邮箱 { email } 的知识库 { ' 已创建 ' if created else ' 已存在 ' } : { kb . id } " )
else :
logger . error ( f " 为邮箱 { email } 创建知识库失败 " )
return kb , created
def set_user_goal ( user , email , goal_content ) :
"""
设置用户总目标
参数 :
user : 用户对象
email : 创作者邮箱
goal_content : 目标内容
返回 :
dict : 包含操作结果和目标信息
"""
try :
# 创建Gmail集成实例
gmail_integration = GmailIntegration ( user )
# 设置总目标
result = gmail_integration . manage_user_goal ( goal_content )
if result [ ' status ' ] == ' success ' :
logger . info ( f " 为用户 { user . username } 设置总目标成功: { result [ ' action ' ] } " )
return result
else :
logger . error ( f " 为用户 { user . username } 设置总目标失败: { result . get ( ' message ' , ' Unknown error ' ) } " )
return result
except Exception as e :
logger . error ( f " 设置用户总目标时出错: { str ( e ) } " )
logger . error ( traceback . format_exc ( ) )
return {
' status ' : ' error ' ,
' message ' : str ( e )
}
def create_chat_with_ai ( user , talent_email , goal_content ) :
"""
创建与AI的聊天会话
参数 :
user : 用户对象
talent_email : 达人邮箱
goal_content : 目标内容
返回 :
dict : 操作结果
"""
try :
# 1. 获取或创建知识库
kb , kb_created = create_or_update_knowledge_base ( talent_email , user )
if not kb :
return {
' status ' : ' error ' ,
' message ' : f " 为邮箱 { talent_email } 创建知识库失败 "
}
# 2. 设置用户总目标
goal_result = set_user_goal ( user , talent_email , goal_content )
if goal_result [ ' status ' ] != ' success ' :
return goal_result
# 3. 检查是否已有对应的Gmail映射关系
mapping = GmailTalentMapping . objects . filter (
user = user ,
talent_email = talent_email ,
is_active = True
) . first ( )
if not mapping :
# 创建映射关系
mapping = GmailTalentMapping . objects . create (
user = user ,
talent_email = talent_email ,
knowledge_base = kb ,
conversation_id = f " feishu_ai_ { kb . id } " ,
is_active = True
)
logger . info ( f " 创建新的Gmail映射: { talent_email } -> { kb . id } " )
elif mapping . knowledge_base_id != kb . id :
# 更新现有映射关系
mapping . knowledge_base = kb
mapping . save ( )
logger . info ( f " 更新Gmail映射: { talent_email } -> { kb . id } " )
return {
' status ' : ' success ' ,
' action ' : ' create ' ,
' knowledge_base ' : {
' id ' : str ( kb . id ) ,
' name ' : kb . name ,
' created ' : kb_created
} ,
' goal ' : goal_result . get ( ' goal ' ) ,
' mapping ' : {
' id ' : str ( mapping . id ) ,
' conversation_id ' : mapping . conversation_id
}
}
except Exception as e :
logger . error ( f " 创建AI聊天时出错: { str ( e ) } " )
logger . error ( traceback . format_exc ( ) )
return {
' status ' : ' error ' ,
' message ' : str ( e )
}
2025-04-29 10:22:57 +08:00
def process_duplicate_emails ( matching_emails , goal_content = None , auto_reply = False ) :
2025-04-17 16:14:00 +08:00
"""
2025-04-29 10:22:57 +08:00
处理匹配到的creator邮箱记录
2025-04-17 16:14:00 +08:00
参数 :
2025-04-29 10:22:57 +08:00
matching_emails : 匹配到的邮箱记录字典
2025-04-17 16:14:00 +08:00
goal_content : 目标内容模板 , 可包含 { email } 和 { handle } 占位符
2025-04-29 10:22:57 +08:00
auto_reply : 是否自动设置Gmail回复
2025-04-17 16:14:00 +08:00
返回 :
dict : 处理结果统计
"""
if not goal_content :
goal_content = " 与达人 {handle} 建立联系并了解其账号情况,处理合作需求,最终目标是达成合作并签约。 "
User = get_user_model ( )
leader = User . objects . filter ( role = ' leader ' ) . first ( )
if not leader :
logger . error ( " 未找到组长用户,无法处理重复邮箱 " )
return {
' status ' : ' error ' ,
' message ' : " 未找到组长用户 "
}
results = {
2025-04-29 10:22:57 +08:00
' total ' : len ( matching_emails ) ,
2025-04-17 16:14:00 +08:00
' success ' : 0 ,
' failure ' : 0 ,
2025-04-29 10:22:57 +08:00
' auto_reply_setup ' : 0 , # 增加自动回复设置计数
2025-04-17 16:14:00 +08:00
' details ' : [ ]
}
2025-04-29 10:22:57 +08:00
for email , records in matching_emails . items ( ) :
2025-04-17 16:14:00 +08:00
try :
# 获取一个Handle作为示例
handle = extract_field_value ( records [ 0 ] . fields . get ( ' Handle ' , email . split ( ' @ ' ) [ 0 ] ) )
# 格式化目标内容
formatted_goal = goal_content . format ( email = email , handle = handle )
# 创建AI聊天
result = create_chat_with_ai ( leader , email , formatted_goal )
if result [ ' status ' ] == ' success ' :
results [ ' success ' ] + = 1
logger . info ( f " 成功为邮箱 { email } 创建AI聊天 " )
2025-04-29 10:22:57 +08:00
# 如果启用自动回复, 设置Gmail监听并开始自动对话
if auto_reply :
try :
# 调用auto_chat_session开始自动对话
auto_chat_result = auto_chat_session ( leader , email )
if auto_chat_result [ ' status ' ] == ' success ' :
results [ ' auto_reply_setup ' ] + = 1
logger . info ( f " 成功为邮箱 { email } 设置自动回复 " )
# 在详情中添加自动回复信息
result [ ' auto_reply ' ] = {
' status ' : ' success ' ,
' message ' : auto_chat_result . get ( ' message ' , ' 已设置自动回复 ' ) ,
' conversation_id ' : auto_chat_result . get ( ' conversation_id ' )
}
else :
logger . error ( f " 为邮箱 { email } 设置自动回复失败: { auto_chat_result . get ( ' message ' , ' Unknown error ' ) } " )
result [ ' auto_reply ' ] = {
' status ' : ' error ' ,
' message ' : auto_chat_result . get ( ' message ' , ' 设置自动回复失败 ' )
}
except Exception as ar_e :
logger . error ( f " 为邮箱 { email } 设置自动回复时出错: { str ( ar_e ) } " )
logger . error ( traceback . format_exc ( ) )
result [ ' auto_reply ' ] = {
' status ' : ' error ' ,
' message ' : str ( ar_e )
}
2025-04-17 16:14:00 +08:00
else :
results [ ' failure ' ] + = 1
logger . error ( f " 为邮箱 { email } 创建AI聊天失败: { result . get ( ' message ' , ' Unknown error ' ) } " )
results [ ' details ' ] . append ( {
' email ' : email ,
' handle ' : handle ,
' status ' : result [ ' status ' ] ,
' message ' : result . get ( ' message ' , ' ' )
} )
except Exception as e :
results [ ' failure ' ] + = 1
logger . error ( f " 处理邮箱 { email } 时出错: { str ( e ) } " )
logger . error ( traceback . format_exc ( ) )
results [ ' details ' ] . append ( {
' email ' : email ,
' status ' : ' error ' ,
' message ' : str ( e )
} )
return results
def generate_ai_response ( conversation_history , user_goal ) :
"""
调用DeepSeek API生成AI响应
2025-04-29 10:22:57 +08:00
2025-04-17 16:14:00 +08:00
参数 :
conversation_history : 对话历史
user_goal : 用户总目标
2025-04-29 10:22:57 +08:00
2025-04-17 16:14:00 +08:00
返回 :
str : AI响应内容
"""
try :
# 使用有效的API密钥
api_key = " sk-xqbujijjqqmlmlvkhvxeogqjtzslnhdtqxqgiyuhwpoqcjvf "
if hasattr ( settings , ' DEEPSEEK_API_KEY ' ) and settings . DEEPSEEK_API_KEY :
api_key = settings . DEEPSEEK_API_KEY
2025-04-29 10:22:57 +08:00
2025-04-17 16:14:00 +08:00
url = " https://api.siliconflow.cn/v1/chat/completions "
2025-04-29 10:22:57 +08:00
2025-04-17 16:14:00 +08:00
system_message = {
2025-04-29 10:22:57 +08:00
" role " : " system " ,
" content " : " 你是一位专业的电商客服和达人助手。你的任务是与达人沟通合作事宜,促成品牌合作。回复简洁专业。 "
}
goal_message = {
" role " : " system " ,
" content " : f " 总目标: { user_goal } "
}
2025-04-17 16:14:00 +08:00
2025-04-29 10:22:57 +08:00
formatted_messages = [ system_message , goal_message ]
recent_history = conversation_history [ - 8 : ] if len ( conversation_history ) > 8 else conversation_history
for msg in recent_history :
role = msg . get ( ' role ' , ' user ' )
if role not in [ ' user ' , ' assistant ' , ' system ' ] :
role = ' user ' if role == ' user ' else ' assistant '
content = msg . get ( ' content ' , ' ' ) . strip ( )
if content :
formatted_messages . append ( {
" role " : role ,
" content " : content
} )
if len ( formatted_messages ) < = 2 :
formatted_messages . append ( {
" role " : " user " ,
" content " : """ 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 """
} )
2025-04-17 16:14:00 +08:00
payload = {
" model " : " deepseek-ai/DeepSeek-V3 " ,
2025-04-29 10:22:57 +08:00
" messages " : formatted_messages ,
2025-04-17 16:14:00 +08:00
" stream " : False ,
2025-04-29 10:22:57 +08:00
" max_tokens " : 512 ,
" temperature " : 0.7 ,
" top_p " : 0.7 ,
2025-04-17 16:14:00 +08:00
" top_k " : 50 ,
" frequency_penalty " : 0.5 ,
" n " : 1 ,
2025-04-29 10:22:57 +08:00
" stop " : [ ]
2025-04-17 16:14:00 +08:00
}
2025-04-29 10:22:57 +08:00
2025-04-17 16:14:00 +08:00
headers = {
2025-04-29 10:22:57 +08:00
" Authorization " : f " Bearer { api_key } " ,
" Content-Type " : " application/json "
2025-04-17 16:14:00 +08:00
}
2025-04-29 10:22:57 +08:00
debug_messages = [ ]
for msg in formatted_messages :
content = msg . get ( ' content ' , ' ' )
if len ( content ) > 50 :
content = content [ : 50 ] + " ... "
debug_messages . append ( { " role " : msg . get ( ' role ' ) , " content " : content } )
logger . info ( f " DeepSeek API请求消息: { debug_messages } " )
2025-04-17 16:14:00 +08:00
logger . info ( " 开始调用DeepSeek API生成对话回复 " )
2025-04-29 10:22:57 +08:00
response = requests . request ( " POST " , url , json = payload , headers = headers )
2025-04-17 16:14:00 +08:00
if response . status_code != 200 :
logger . error ( f " DeepSeek API调用失败: { response . status_code } , { response . text } " )
2025-04-29 10:22:57 +08:00
return " AI生成失败, 请稍后重试 "
2025-04-17 16:14:00 +08:00
result = response . json ( )
logger . debug ( f " DeepSeek API返回: { result } " )
2025-04-29 10:22:57 +08:00
2025-04-17 16:14:00 +08:00
if ' choices ' in result and len ( result [ ' choices ' ] ) > 0 :
reply = result [ ' choices ' ] [ 0 ] [ ' message ' ] [ ' content ' ]
if not reply or reply . strip ( ) == ' ' :
logger . warning ( " DeepSeek API返回的回复内容为空 " )
2025-04-29 10:22:57 +08:00
return " AI生成失败, 请稍后重试 "
logger . info ( f " DeepSeek API成功生成回复: { reply [ : 50 ] } ... " )
2025-04-17 16:14:00 +08:00
return reply
2025-04-29 10:22:57 +08:00
2025-04-17 16:14:00 +08:00
logger . warning ( f " DeepSeek API返回格式异常: { result } " )
2025-04-29 10:22:57 +08:00
return " AI生成失败, 请稍后重试 "
2025-04-17 16:14:00 +08:00
except Exception as e :
logger . error ( f " 调用DeepSeek API生成回复失败: { str ( e ) } " )
logger . error ( traceback . format_exc ( ) )
2025-04-29 10:22:57 +08:00
return " AI生成失败, 请稍后重试 "
2025-04-17 16:14:00 +08:00
def check_goal_achieved ( response ) :
"""
检查目标是否已达成
参数 :
response : AI的回复内容
返回 :
bool : 是否达成目标
"""
if not response :
return False
# 检查回复中是否包含目标达成标记
goal_markers = [
" [目标已达成] " ,
" 【目标已达成】 " ,
" 目标已达成 " ,
" 已达成目标 "
]
for marker in goal_markers :
if marker in response :
logger . info ( f " 检测到目标达成标记: { marker } " )
return True
return False
def auto_chat_session ( user , talent_email , max_turns = 10 ) :
"""
执行自动聊天会话
参数 :
user : 用户对象
talent_email : 达人邮箱
max_turns : 最大对话轮次 , 默认10轮
返回 :
dict : 会话结果
"""
try :
2025-04-29 10:22:57 +08:00
# 从用户关联的Gmail凭证中查找有效的凭证
from user_management . models import GmailCredential
# 查找用户的有效Gmail凭证, 优先使用默认账户
gmail_credential = GmailCredential . objects . filter (
user = user ,
is_active = True ,
is_default = True
) . first ( )
# 如果没有默认账户,使用任何一个有效账户
if not gmail_credential :
gmail_credential = GmailCredential . objects . filter (
user = user ,
is_active = True
) . first ( )
if gmail_credential :
gmail_credential_id = str ( gmail_credential . id )
logger . info ( f " 找到用户 { user . email } 的Gmail凭证, ID: { gmail_credential . id } , Gmail: { gmail_credential . gmail_email or ' 未知 ' } ,名称: { gmail_credential . name } " )
else :
logger . warning ( f " 用户 { user . email } 没有有效的Gmail凭证 " )
gmail_credential_id = None
2025-04-17 16:14:00 +08:00
# 1. 获取用户目标
2025-04-29 10:22:57 +08:00
gmail_integration = GmailIntegration (
user = user ,
gmail_credential_id = gmail_credential_id
)
2025-04-17 16:14:00 +08:00
goal_result = gmail_integration . manage_user_goal ( )
if goal_result [ ' status ' ] != ' success ' or not goal_result . get ( ' goal ' ) :
logger . error ( f " 获取用户目标失败: { goal_result . get ( ' message ' , ' No goal found ' ) } " )
return {
' status ' : ' error ' ,
' message ' : " 无法获取用户总目标 "
}
user_goal = goal_result [ ' goal ' ] [ ' content ' ]
# 2. 获取Gmail映射关系
mapping = GmailTalentMapping . objects . filter (
user = user ,
talent_email = talent_email ,
is_active = True
) . first ( )
if not mapping :
logger . error ( f " 找不到与邮箱 { talent_email } 的映射关系 " )
return {
' status ' : ' error ' ,
' message ' : f " 找不到与邮箱 { talent_email } 的映射关系 "
}
kb = mapping . knowledge_base
conversation_id = mapping . conversation_id
# 3. 获取现有对话历史
chat_messages = ChatHistory . objects . filter (
user = user ,
knowledge_base = kb ,
conversation_id = conversation_id ,
is_deleted = False
) . order_by ( ' created_at ' )
conversation_history = [ ]
for msg in chat_messages :
conversation_history . append ( {
" role " : msg . role ,
" content " : msg . content
} )
# 如果没有对话历史,添加一条系统消息作为开始
if not conversation_history :
# 创建一条初始的系统消息
system_msg = ChatHistory . objects . create (
user = user ,
knowledge_base = kb ,
conversation_id = conversation_id ,
role = ' system ' ,
content = f " 与达人 { talent_email } 的对话开始。总目标: { user_goal } "
)
conversation_history . append ( {
" role " : " system " ,
" content " : system_msg . content
} )
# 发送第一封邮件来开始对话
first_subject = " 关于合作的洽谈 "
2025-04-29 10:22:57 +08:00
first_content = f " Paid Collaboration Opportunity with TikTok ' s #1 Fragrance Brand 🌸 \n Hi, \n 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. \n 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. \n We ' d love to explore a partnership and would appreciate it if you could share: \n Your rate for a single TikTok video \n Whether you offer bundle pricing for multiple videos \n Any additional details or formats you offer (e.g. story integration, livestream add-ons, etc.) \n The product has strong market traction, proven conversions, and a competitive commission structure if you ' re also open to affiliate partnerships. \n Looking forward to the opportunity to work together and hearing your rates! \n Warm regards, \n Vira \n OOIN Media "
2025-04-17 16:14:00 +08:00
# 记录首次发送消息到对话历史
initial_msg = ChatHistory . objects . create (
user = user ,
knowledge_base = kb ,
conversation_id = conversation_id ,
2025-04-29 10:22:57 +08:00
role = ' user ' , # 修正: 系统发送的首次消息, 角色是user
2025-04-17 16:14:00 +08:00
content = first_content
)
conversation_history . append ( {
2025-04-29 10:22:57 +08:00
" role " : " user " , # 修正: 系统发送的首次消息, 角色是user
2025-04-17 16:14:00 +08:00
" content " : initial_msg . content
} )
# 实际发送邮件
2025-04-29 10:22:57 +08:00
try :
email_result = gmail_integration . send_email (
to_email = talent_email ,
subject = first_subject ,
body = first_content ,
conversation_id = conversation_id
)
gmail_account = gmail_credential . gmail_email if gmail_credential and gmail_credential . gmail_email else " 未设置 "
logger . info ( f " 已发送首次邮件到 { talent_email } , 使用Gmail账号: { gmail_account } , 消息ID: { email_result } " )
# 设置Gmail监听, 确保能接收到达人的回复
gmail_integration . setup_watch ( )
gmail_account = gmail_credential . gmail_email if gmail_credential and gmail_credential . gmail_email else " 未设置 "
logger . info ( f " 已为Gmail账号 { gmail_account } 设置监听 " )
return {
' status ' : ' success ' ,
' message ' : ' 已发送首次邮件并设置Gmail监听, 等待达人回复 ' ,
' turns_completed ' : 1 ,
' goal_achieved ' : False ,
' email_sent ' : True ,
' conversation_id ' : conversation_id
}
except Exception as e :
logger . error ( f " 发送首次邮件失败: { str ( e ) } " )
2025-04-17 16:14:00 +08:00
return {
' status ' : ' error ' ,
2025-04-29 10:22:57 +08:00
' message ' : f " 发送首次邮件失败: { str ( e ) } "
2025-04-17 16:14:00 +08:00
}
2025-04-29 10:22:57 +08:00
# 4. 检查最新消息是否来自达人(role=assistant), 如果是, 则生成AI回复
last_message = chat_messages . last ( )
# 添加详细日志,记录最新消息信息
if last_message :
logger . info ( f " 最新消息ID: { last_message . id } , 角色: { last_message . role } , 创建时间: { last_message . created_at } " )
2025-04-17 16:14:00 +08:00
2025-04-29 10:22:57 +08:00
# 检查更详细的元数据
if hasattr ( last_message , ' metadata ' ) and last_message . metadata :
logger . info ( f " 最新消息元数据: { last_message . metadata } " )
# 检查最新消息是否是今天的
from django . utils import timezone
today = timezone . now ( ) . date ( )
msg_date = last_message . created_at . date ( )
is_today = ( msg_date == today )
logger . info ( f " 最新消息日期: { msg_date } , 是否是今天: { is_today } " )
else :
logger . info ( " 没有找到任何消息记录 " )
# 先尝试刷新Gmail邮件, 确保获取最新消息
try :
# 获取最近的几封邮件并处理
recent_emails = gmail_integration . get_recent_emails ( from_email = talent_email , max_results = 3 )
if recent_emails :
logger . info ( f " 尝试处理最近的 { len ( recent_emails ) } 封来自 { talent_email } 的邮件 " )
result = gmail_integration . save_conversations_to_knowledge_base ( recent_emails , kb )
logger . info ( f " 刷新处理结果: { result } " )
# 重新获取对话历史,确保包含最新消息
chat_messages = ChatHistory . objects . filter (
user = user ,
knowledge_base = kb ,
conversation_id = conversation_id ,
is_deleted = False
) . order_by ( ' created_at ' )
# 更新最新消息
last_message = chat_messages . last ( )
if last_message :
logger . info ( f " 刷新后的最新消息ID: { last_message . id } , 角色: { last_message . role } , 创建时间: { last_message . created_at } " )
except Exception as refresh_error :
logger . error ( f " 尝试刷新最新邮件失败: { str ( refresh_error ) } " )
logger . error ( traceback . format_exc ( ) )
# 根据消息的role判断是否是达人回复
# 在系统中,达人的回复角色为'assistant',系统自动生成的回复角色为'user'
has_talent_reply = False
if last_message and last_message . role == ' assistant ' :
# 检查这条消息是否已经处理过(是否已经针对此消息生成了回复)
has_response = ChatHistory . objects . filter (
user = user ,
knowledge_base = kb ,
conversation_id = conversation_id ,
parent_id = str ( last_message . id ) ,
role = ' user ' , # 系统AI自动回复角色是user
is_deleted = False
) . exists ( )
2025-04-17 16:14:00 +08:00
2025-04-29 10:22:57 +08:00
if has_response :
logger . info ( f " 最新达人消息 { last_message . id } 已经有回复,不再生成新回复 " )
has_talent_reply = False
else :
logger . info ( f " 发现尚未回复的达人消息: { last_message . id } " )
has_talent_reply = True
2025-04-17 16:14:00 +08:00
2025-04-29 10:22:57 +08:00
# 检查是否有新的达人回复
if has_talent_reply :
# 有达人回复, 生成AI回复
2025-04-17 16:14:00 +08:00
ai_response = generate_ai_response ( conversation_history , user_goal )
if not ai_response :
logger . error ( " 生成AI回复失败 " )
return {
' status ' : ' error ' ,
' message ' : ' 生成AI回复失败 '
}
# 检查目标是否已达成
goal_achieved = check_goal_achieved ( ai_response )
# 保存AI回复到对话历史
ai_msg = ChatHistory . objects . create (
user = user ,
knowledge_base = kb ,
conversation_id = conversation_id ,
2025-04-29 10:22:57 +08:00
role = ' user ' , # 修正: 系统AI自动回复角色是user
content = ai_response ,
parent_id = str ( last_message . id ) # 明确设置父消息ID为达人消息ID
2025-04-17 16:14:00 +08:00
)
2025-04-29 10:22:57 +08:00
logger . info ( f " 已保存AI回复: ID= { ai_msg . id } , 父消息ID= { last_message . id } " )
2025-04-17 16:14:00 +08:00
# 使用最近的主题作为回复主题
2025-04-29 10:22:57 +08:00
subject = " 回复: " + ( last_message . metadata . get ( ' subject ' ) if hasattr ( last_message , ' metadata ' ) and last_message . metadata and ' subject ' in last_message . metadata else " 关于合作的洽谈 " )
2025-04-17 16:14:00 +08:00
# 实际发送邮件
2025-04-29 10:22:57 +08:00
try :
email_result = gmail_integration . send_email (
to_email = talent_email ,
subject = subject ,
body = ai_response ,
conversation_id = conversation_id
)
gmail_account = gmail_credential . gmail_email if gmail_credential and gmail_credential . gmail_email else " 未设置 "
logger . info ( f " 已发送邮件回复到 { talent_email } , 使用Gmail账号: { gmail_account } , 消息ID: { email_result } " )
except Exception as e :
logger . error ( f " 发送邮件回复失败: { str ( e ) } " )
logger . error ( traceback . format_exc ( ) )
2025-04-17 16:14:00 +08:00
return {
' status ' : ' error ' ,
2025-04-29 10:22:57 +08:00
' message ' : f " 发送邮件回复失败: { str ( e ) } "
2025-04-17 16:14:00 +08:00
}
# 如果目标已达成,发送通知
if goal_achieved :
send_goal_achieved_notification ( user , talent_email , conversation_id )
# 生成对话总结
summary_result = gmail_integration . generate_conversation_summary ( talent_email )
summary_status = " success " if summary_result . get ( ' status ' ) == ' success ' else " failed "
else :
summary_status = " not_needed "
return {
' status ' : ' success ' ,
' turns_completed ' : 1 ,
' goal_achieved ' : goal_achieved ,
' summary_status ' : summary_status ,
' email_sent ' : True ,
' conversation_id ' : conversation_id
}
else :
2025-04-29 10:22:57 +08:00
# 没有新的达人回复, 确保Gmail监听已设置
try :
# 检查并更新Gmail监听
gmail_integration . setup_watch ( )
gmail_account = gmail_credential . gmail_email if gmail_credential and gmail_credential . gmail_email else " 未设置 "
logger . info ( f " 已更新Gmail账号 { gmail_account } 的监听 " )
return {
' status ' : ' success ' ,
' message ' : ' 没有新的达人回复, 已更新Gmail监听, 将继续等待回复 ' ,
' turns_completed ' : 0 ,
' goal_achieved ' : False ,
' email_sent ' : False ,
' conversation_id ' : conversation_id
}
except Exception as e :
logger . error ( f " 更新Gmail监听失败: { str ( e ) } " )
return {
' status ' : ' error ' ,
' message ' : f " 更新Gmail监听失败: { str ( e ) } "
}
2025-04-17 16:14:00 +08:00
except Exception as e :
logger . error ( f " 自动聊天会话出错: { str ( e ) } " )
logger . error ( traceback . format_exc ( ) )
return {
' status ' : ' error ' ,
' message ' : str ( e )
}
def send_goal_achieved_notification ( user , talent_email , conversation_id ) :
"""
发送目标达成通知
参数 :
user : 用户对象
talent_email : 达人邮箱
conversation_id : 对话ID
"""
try :
from user_management . models import Notification
# 创建通知
notification = Notification . objects . create (
type = ' system_notice ' ,
title = f " 目标已达成 - { talent_email } " ,
content = f " 与达人 { talent_email } 的自动对话已达成总目标。请查看对话详情并进行后续处理。 " ,
sender = user , # 这里发送者设为当前用户
receiver = user , # 通知发给当前用户
related_resource = conversation_id
)
logger . info ( f " 已创建目标达成通知: { notification . id } " )
except Exception as e :
logger . error ( f " 发送目标达成通知失败: { str ( e ) } " )
logger . error ( traceback . format_exc ( ) )
2025-04-29 10:22:57 +08:00
def export_matching_emails_to_excel ( matching_emails , records , output_path = None ) :
"""
将匹配到的邮箱数据导出到Excel文件
参数 :
matching_emails : 匹配到的邮箱字典
records : 原始飞书记录
output_path : 输出文件路径 , 默认为当前目录下的 ' matching_emails_ {timestamp} .xlsx '
返回 :
str : 导出文件的路径
"""
try :
# 如果未指定输出路径,创建默认路径
if not output_path :
timestamp = datetime . now ( ) . strftime ( ' % Y % m %d _ % H % M % S ' )
output_path = f ' matching_emails_ { timestamp } .xlsx '
# 创建数据列表
data = [ ]
# 遍历匹配的邮箱记录
for email , email_records in matching_emails . items ( ) :
for record in email_records :
fields = record . fields
row = {
' Email ' : email ,
' RecordID ' : record . record_id
}
# 提取所有字段
for field_name , field_value in fields . items ( ) :
row [ field_name ] = extract_field_value ( field_value )
data . append ( row )
# 如果没有数据,返回错误
if not data :
logger . error ( " 没有匹配的邮箱数据可以导出 " )
return None
# 创建DataFrame并导出到Excel
df = pd . DataFrame ( data )
df . to_excel ( output_path , index = False , engine = ' openpyxl ' )
logger . info ( f " 成功导出匹配的邮箱数据到: { output_path } " )
return output_path
except Exception as e :
logger . error ( f " 导出匹配邮箱数据到Excel时出错: { str ( e ) } " )
logger . error ( traceback . format_exc ( ) )
return None
def export_feishu_creators_to_excel ( matching_emails , output_path = None ) :
"""
将匹配到的FeishuCreator数据导出到Excel文件
参数 :
matching_emails : 匹配到的邮箱字典
output_path : 输出文件路径 , 默认为当前目录下的 ' feishu_creators_ {timestamp} .xlsx '
返回 :
str : 导出文件的路径
"""
try :
# 如果未指定输出路径,创建默认路径
if not output_path :
timestamp = datetime . now ( ) . strftime ( ' % Y % m %d _ % H % M % S ' )
output_path = f ' feishu_creators_ { timestamp } .xlsx '
# 创建数据列表
data = [ ]
# 获取所有匹配的邮箱
emails = list ( matching_emails . keys ( ) )
# 从数据库获取FeishuCreator记录
creators = FeishuCreator . objects . filter ( email__in = emails )
if not creators . exists ( ) :
logger . error ( " 没有找到匹配的FeishuCreator记录 " )
return None
# 遍历Creator记录并添加到数据列表
for creator in creators :
# 处理datetime字段, 移除时区信息
created_at = creator . created_at
if hasattr ( created_at , ' tzinfo ' ) and created_at . tzinfo is not None :
created_at = created_at . replace ( tzinfo = None )
updated_at = creator . updated_at
if hasattr ( updated_at , ' tzinfo ' ) and updated_at . tzinfo is not None :
updated_at = updated_at . replace ( tzinfo = None )
row = {
' id ' : str ( creator . id ) , # 转换UUID为字符串
' handle ' : creator . handle , # 使用handle代替name
' email ' : creator . email ,
' phone ' : creator . phone , # 使用phone代替phone_number
' social_platform ' : ' TikTok ' , # 假设平台为TikTok
' fans_count ' : creator . fans_count , # 粉丝数
' account_type ' : creator . account_type , # 账号属性
' region ' : creator . creator_base , # 使用creator_base作为region
' cooperation_intention ' : creator . cooperation_intention , # 合作意向
' created_at ' : created_at ,
' updated_at ' : updated_at ,
' is_deleted ' : False , # FeishuCreator没有is_deleted字段, 默认为False
' remarks ' : creator . notes , # 使用notes作为remarks
' status ' : creator . contact_status # 使用contact_status作为status
}
# 添加其他可能有用的字段
row [ ' price_quote ' ] = creator . price_quote
row [ ' payment_method ' ] = creator . payment_method
row [ ' contact_person ' ] = creator . contact_person
row [ ' tiktok_url ' ] = creator . tiktok_url
row [ ' gmv ' ] = creator . gmv
row [ ' has_ooin ' ] = creator . has_ooin
row [ ' address ' ] = creator . address
data . append ( row )
# 创建DataFrame并导出到Excel
df = pd . DataFrame ( data )
# 尝试使用不同的Excel引擎
try :
# 首先尝试使用openpyxl
df . to_excel ( output_path , index = False , engine = ' openpyxl ' )
except ( ImportError , ModuleNotFoundError ) :
try :
# 如果openpyxl不可用, 尝试使用xlsxwriter
df . to_excel ( output_path , index = False , engine = ' xlsxwriter ' )
except ( ImportError , ModuleNotFoundError ) :
# 如果两者都不可用,使用默认引擎
df . to_excel ( output_path , index = False )
logger . info ( f " 成功导出FeishuCreator数据到: { output_path } " )
return output_path
except Exception as e :
logger . error ( f " 导出FeishuCreator数据到Excel时出错: { str ( e ) } " )
logger . error ( traceback . format_exc ( ) )
return None
def process_gmail_notification ( notification_data , user = None ) :
"""
处理Gmail推送通知 , 在收到达人回复时自动生成AI回复
参数 :
notification_data : Gmail推送通知数据
user : 用户对象 , 如果为None则使用通知中的邮箱查找用户
返回 :
dict : 处理结果
"""
try :
# 导入所需模型
from user_management . models import User , GmailCredential , GmailTalentMapping , ChatHistory
from user_management . gmail_integration import GmailIntegration
# 1. 从通知数据中提取信息
# 处理Google Pub/Sub消息格式
if isinstance ( notification_data , dict ) and ' message ' in notification_data and ' data ' in notification_data [ ' message ' ] :
try :
import base64
import json
logger . info ( " 检测到Google Pub/Sub消息格式 " )
# Base64解码data字段
encoded_data = notification_data [ ' message ' ] [ ' data ' ]
decoded_data = base64 . b64decode ( encoded_data ) . decode ( ' utf-8 ' )
logger . info ( f " 解码后的数据: { decoded_data } " )
# 解析JSON获取email和historyId
json_data = json . loads ( decoded_data )
email = json_data . get ( ' emailAddress ' )
history_id = json_data . get ( ' historyId ' )
logger . info ( f " 从Pub/Sub消息中提取: email= { email } , historyId= { history_id } " )
except Exception as decode_error :
logger . error ( f " 解析Pub/Sub消息失败: { str ( decode_error ) } " )
logger . error ( traceback . format_exc ( ) )
return { ' status ' : ' error ' , ' message ' : f " 解析通知失败: { str ( decode_error ) } " }
else :
# 原始格式处理
email = notification_data . get ( ' emailAddress ' )
history_id = notification_data . get ( ' historyId ' )
if not email or not history_id :
logger . error ( " Gmail通知数据不完整: 找不到emailAddress或historyId " )
return { ' status ' : ' error ' , ' message ' : " 通知数据不完整 " }
# 2. 查找关联用户
if not user :
user = User . objects . filter ( email = email ) . first ( )
# 如果找不到用户, 尝试使用gmail_email字段查找
if not user :
logger . info ( f " 找不到email= { email } 的用户, 尝试使用gmail_email查找 " )
credential = GmailCredential . objects . filter ( gmail_email = email , is_active = True ) . first ( )
if credential :
user = credential . user
logger . info ( f " 通过gmail_email找到用户: { user . email } " )
if not user :
logger . error ( f " 找不到与邮箱 { email } 关联的用户 " )
return { ' status ' : ' error ' , ' message ' : f " 找不到与邮箱 { email } 关联的用户 " }
# 3. 初始化Gmail集成
gmail_integration = GmailIntegration ( user )
# 4. 获取历史记录变更
message_ids = gmail_integration . get_history ( history_id )
if not message_ids :
logger . info ( f " 没有新消息: historyId= { history_id } " )
return { ' status ' : ' success ' , ' message ' : " 没有新消息 " , ' processed ' : 0 }
# 5. 处理新消息
processed_count = 0
for message_id in message_ids :
# 获取邮件详情
message = gmail_integration . gmail_service . users ( ) . messages ( ) . get (
userId = ' me ' , id = message_id
) . execute ( )
# 提取邮件内容
email_data = gmail_integration . _extract_email_content ( message )
if not email_data :
logger . error ( f " 提取邮件内容失败: { message_id } " )
continue
# 获取发件人邮箱
from_email = email_data . get ( ' from ' , ' ' )
sender_email = ' '
if ' < ' in from_email and ' > ' in from_email :
# 格式如 "姓名 <email@example.com>"
sender_email = from_email . split ( ' < ' ) [ 1 ] . split ( ' > ' ) [ 0 ]
else :
# 格式可能直接是邮箱
sender_email = from_email
# 检查是否是用户自己发送的邮件
if sender_email . lower ( ) == user . email . lower ( ) :
logger . info ( f " 跳过用户自己发送的邮件: { message_id } " )
continue
# 查找与发件人关联的Gmail达人映射
talent_mapping = GmailTalentMapping . objects . filter (
user = user ,
talent_email = sender_email ,
is_active = True
) . first ( )
if not talent_mapping :
logger . info ( f " 找不到与发件人 { sender_email } 的映射关系,跳过处理 " )
continue
# 获取知识库和对话ID
kb = talent_mapping . knowledge_base
conversation_id = talent_mapping . conversation_id
# 检查消息是否已经处理过
if ChatHistory . objects . filter (
conversation_id = conversation_id ,
metadata__gmail_message_id = message_id ,
is_deleted = False
) . exists ( ) :
logger . info ( f " 邮件已处理过,跳过: { message_id } " )
continue
# 记录达人回复到对话历史
metadata = {
' gmail_message_id ' : message_id ,
' from ' : from_email ,
' date ' : email_data . get ( ' date ' , ' ' ) ,
' subject ' : email_data . get ( ' subject ' , ' ' )
}
# 保存达人回复到对话历史, 角色为assistant
chat_message = ChatHistory . objects . create (
user = user ,
knowledge_base = kb ,
conversation_id = conversation_id ,
role = ' assistant ' , # 达人回复角色为assistant
content = f " [ { email_data [ ' subject ' ] } ] { email_data [ ' body ' ] } " ,
metadata = metadata
)
# 获取对话历史
chat_messages = ChatHistory . objects . filter (
user = user ,
knowledge_base = kb ,
conversation_id = conversation_id ,
is_deleted = False
) . order_by ( ' created_at ' )
# 检查消息是否已经处理过并有回复
has_response = ChatHistory . objects . filter (
user = user ,
knowledge_base = kb ,
conversation_id = conversation_id ,
parent_id = str ( chat_message . id ) ,
role = ' user ' , # 系统回复角色是user
is_deleted = False
) . exists ( )
if has_response :
logger . info ( f " 达人消息 { message_id } 已有回复,跳过自动回复生成 " )
continue
conversation_history = [ ]
for msg in chat_messages :
conversation_history . append ( {
" role " : msg . role ,
" content " : msg . content
} )
# 获取用户目标
goal_result = gmail_integration . manage_user_goal ( )
if goal_result [ ' status ' ] != ' success ' or not goal_result . get ( ' goal ' ) :
logger . error ( f " 获取用户目标失败: { goal_result . get ( ' message ' , ' No goal found ' ) } " )
continue
user_goal = goal_result [ ' goal ' ] [ ' content ' ]
# 生成AI回复
ai_response = generate_ai_response ( conversation_history , user_goal )
if not ai_response :
logger . error ( " 生成AI回复失败 " )
continue
# 检查目标是否已达成
goal_achieved = check_goal_achieved ( ai_response )
# 保存AI回复到对话历史
ai_msg = ChatHistory . objects . create (
user = user ,
knowledge_base = kb ,
conversation_id = conversation_id ,
role = ' user ' , # 系统AI回复角色为user
content = ai_response
)
# 使用最近的主题作为回复主题
subject = " 回复: " + ( email_data . get ( ' subject ' , ' 关于合作的洽谈 ' ) )
# 发送AI回复邮件
try :
email_result = gmail_integration . send_email (
to_email = sender_email ,
subject = subject ,
body = ai_response ,
conversation_id = conversation_id
)
logger . info ( f " 已发送邮件回复到 { sender_email } , 消息ID: { email_result } " )
processed_count + = 1
# 如果目标已达成,发送通知
if goal_achieved :
send_goal_achieved_notification ( user , sender_email , conversation_id )
# 生成对话总结
summary_result = gmail_integration . generate_conversation_summary ( sender_email )
logger . info ( f " 目标已达成,生成对话总结: { summary_result . get ( ' status ' , ' failed ' ) } " )
except Exception as e :
logger . error ( f " 发送邮件回复失败: { str ( e ) } " )
logger . error ( traceback . format_exc ( ) )
return {
' status ' : ' error ' ,
' message ' : f " 发送邮件回复失败: { str ( e ) } "
}
# 返回处理结果
return {
' status ' : ' success ' ,
' message ' : f " 成功处理 { processed_count } 条新消息 " ,
' processed ' : processed_count
}
except Exception as e :
logger . error ( f " 处理Gmail通知失败: { str ( e ) } " )
logger . error ( traceback . format_exc ( ) )
return {
' status ' : ' error ' ,
' message ' : str ( e )
}
2025-04-17 16:14:00 +08:00
def print_help ( ) :
""" 打印帮助信息 """
print ( " 飞书多维表格自动AI对话工具 " )
print ( " ======================= " )
print ( " \n 可用命令: " )
print ( " 1. 从飞书读取表格并处理重复邮箱: " )
print ( " python feishu_ai_chat.py process_table --app_token 应用TOKEN --table_id 表格ID --view_id 视图ID --access_token 访问令牌 " )
2025-04-29 10:22:57 +08:00
print ( " 选项: " )
print ( " --export_excel 导出匹配的邮箱数据到Excel " )
print ( " --export_creators 导出匹配的FeishuCreator数据到Excel " )
print ( " --output_path 文件路径 指定Excel导出文件路径 " )
2025-04-17 16:14:00 +08:00
print ( )
print ( " 2. 为指定邮箱执行自动对话: " )
print ( " python feishu_ai_chat.py auto_chat --email 达人邮箱 [--turns 对话轮数] " )
print ( )
print ( " 3. 设置用户总目标: " )
print ( " python feishu_ai_chat.py set_goal --email 达人邮箱 --goal \" 总目标内容 \" " )
print ( )
print ( " 4. 检查目标完成状态: " )
print ( " python feishu_ai_chat.py check_goal --email 达人邮箱 " )
print ( )
2025-04-29 10:22:57 +08:00
print ( " 5. 导出匹配的邮箱数据到Excel: " )
print ( " python feishu_ai_chat.py export_excel --app_token 应用TOKEN --table_id 表格ID --view_id 视图ID [--output_path 文件路径] " )
print ( )
print ( " 6. 导出匹配的FeishuCreator数据到Excel: " )
print ( " python feishu_ai_chat.py export_creators --app_token 应用TOKEN --table_id 表格ID --view_id 视图ID [--output_path 文件路径] " )
print ( )
print ( " 7. 处理Gmail通知: " )
print ( " python feishu_ai_chat.py process_notification --data 通知数据JSON或文件路径 [--user_email 用户邮箱] " )
print ( )
print ( " 8. 导出所有达人回复数据到Excel: " )
print ( " python feishu_ai_chat.py export_talent_replies [--user_email 用户邮箱] [--output_path 文件路径] " )
print ( )
print ( " 9. 帮助信息: " )
2025-04-17 16:14:00 +08:00
print ( " python feishu_ai_chat.py help " )
print ( )
print ( " 示例: " )
2025-04-29 10:22:57 +08:00
print ( " python feishu_ai_chat.py process_table --table_id tblcck2za8GZBliz --view_id vewSOIsmxc --export_excel " )
print ( " python feishu_ai_chat.py export_creators --table_id tblcck2za8GZBliz --view_id vewSOIsmxc " )
2025-04-17 16:14:00 +08:00
print ( " python feishu_ai_chat.py auto_chat --email example@gmail.com --turns 5 " )
2025-04-29 10:22:57 +08:00
print ( " python feishu_ai_chat.py export_talent_replies --user_email admin@example.com " )
2025-04-17 16:14:00 +08:00
print ( )
def handle_process_table ( args ) :
""" 处理飞书表格命令 """
import argparse
parser = argparse . ArgumentParser ( description = ' 处理飞书表格 ' )
parser . add_argument ( ' --app_token ' , default = " XYE6bMQUOaZ5y5svj4vcWohGnmg " , help = ' 飞书应用TOKEN ' )
parser . add_argument ( ' --table_id ' , required = True , help = ' 表格ID ' )
parser . add_argument ( ' --view_id ' , required = True , help = ' 视图ID ' )
parser . add_argument ( ' --access_token ' , default = None , help = ' 用户访问令牌 ' )
parser . add_argument ( ' --app_id ' , default = " cli_a5c97daacb9e500d " , help = ' 应用ID ' )
parser . add_argument ( ' --app_secret ' , default = " fdVeOCLXmuIHZVmSV0VbJh9wd0Kq1o5y " , help = ' 应用密钥 ' )
parser . add_argument ( ' --goal_template ' , default = " 与达人 {handle} (邮箱: {email} )建立联系并了解其账号情况,评估合作潜力,处理合作需求,最终目标是达成合作并签约。 " , help = ' 目标内容模板 ' )
parser . add_argument ( ' --auto_chat ' , action = ' store_true ' , help = ' 是否自动执行AI对话 ' )
2025-04-29 10:22:57 +08:00
parser . add_argument ( ' --auto_reply ' , action = ' store_true ' , help = ' 是否自动设置Gmail回复 ' )
2025-04-17 16:14:00 +08:00
parser . add_argument ( ' --turns ' , type = int , default = 5 , help = ' 自动对话轮次 ' )
2025-04-29 10:22:57 +08:00
parser . add_argument ( ' --export_excel ' , action = ' store_true ' , help = ' 是否导出匹配的邮箱数据到Excel ' )
parser . add_argument ( ' --export_creators ' , action = ' store_true ' , help = ' 是否导出匹配的FeishuCreator数据到Excel ' )
parser . add_argument ( ' --output_path ' , help = ' Excel导出文件路径 ' )
2025-04-17 16:14:00 +08:00
params = parser . parse_args ( args )
# 从飞书表格获取记录
records = fetch_table_records (
params . app_token ,
params . table_id ,
params . view_id ,
params . access_token ,
params . app_id ,
params . app_secret
)
if not records :
logger . error ( " 未获取到任何记录 " )
return
# 查找重复邮箱的创作者
duplicate_emails = find_duplicate_email_creators ( records )
if not duplicate_emails :
2025-04-29 10:22:57 +08:00
logger . info ( " 未找到与系统中已有creator匹配的邮箱 " )
2025-04-17 16:14:00 +08:00
return
2025-04-29 10:22:57 +08:00
logger . info ( f " 发现 { len ( duplicate_emails ) } 个匹配的邮箱,开始处理... " )
2025-04-17 16:14:00 +08:00
# 处理重复邮箱记录
2025-04-29 10:22:57 +08:00
results = process_duplicate_emails ( duplicate_emails , params . goal_template , params . auto_reply )
2025-04-17 16:14:00 +08:00
# 打印处理结果
logger . info ( f " 处理完成: 总计 { results [ ' total ' ] } 个邮箱,成功 { results [ ' success ' ] } 个,失败 { results [ ' failure ' ] } 个 " )
2025-04-29 10:22:57 +08:00
# 如果启用了自动回复,显示回复设置结果
if params . auto_reply :
logger . info ( f " 自动回复设置: 成功 { results . get ( ' auto_reply_setup ' , 0 ) } 个 " )
# 如果需要导出邮箱Excel
if params . export_excel :
output_path = export_matching_emails_to_excel ( duplicate_emails , records , params . output_path )
if output_path :
logger . info ( f " 已将匹配的邮箱数据导出到: { output_path } " )
else :
logger . error ( " 导出邮箱Excel失败 " )
# 如果需要导出FeishuCreator Excel
if params . export_creators :
output_path = export_feishu_creators_to_excel ( duplicate_emails , params . output_path )
if output_path :
logger . info ( f " 已将匹配的FeishuCreator数据导出到: { output_path } " )
else :
logger . error ( " 导出FeishuCreator Excel失败 " )
2025-04-17 16:14:00 +08:00
# 如果需要自动对话
if params . auto_chat :
# 获取组长用户
User = get_user_model ( )
leader = User . objects . filter ( role = ' leader ' ) . first ( )
if not leader :
logger . error ( " 未找到组长用户,无法进行自动对话 " )
return
# 为每个成功创建的记录执行自动对话
chat_results = [ ]
for detail in results [ ' details ' ] :
if detail [ ' status ' ] == ' success ' :
email = detail [ ' email ' ]
logger . info ( f " 开始为邮箱 { email } 执行自动对话... " )
chat_result = auto_chat_session ( leader , email , max_turns = params . turns )
chat_results . append ( {
' email ' : email ,
' result ' : chat_result
} )
logger . info ( f " 邮箱 { email } 自动对话完成: { chat_result [ ' status ' ] } " )
# 打印对话结果
logger . info ( f " 自动对话完成: 总计 { len ( chat_results ) } 个对话 " )
for chat in chat_results :
result = chat [ ' result ' ]
if result [ ' status ' ] == ' success ' :
logger . info ( f " 邮箱 { chat [ ' email ' ] } 对话成功,轮次: { result [ ' turns_completed ' ] } ,目标达成: { result [ ' goal_achieved ' ] } " )
else :
logger . info ( f " 邮箱 { chat [ ' email ' ] } 对话失败: { result . get ( ' message ' , ' Unknown error ' ) } " )
def handle_auto_chat ( args ) :
""" 处理自动对话命令 """
import argparse
parser = argparse . ArgumentParser ( description = ' 执行自动对话 ' )
parser . add_argument ( ' --email ' , required = True , help = ' 达人邮箱 ' )
parser . add_argument ( ' --force_send ' , action = ' store_true ' , help = ' 是否强制发送新邮件 ' )
parser . add_argument ( ' --subject ' , help = ' 邮件主题(仅当force_send=true时使用) ' )
parser . add_argument ( ' --content ' , help = ' 邮件内容(仅当force_send=true时使用) ' )
params = parser . parse_args ( args )
# 获取组长用户
User = get_user_model ( )
leader = User . objects . filter ( role = ' leader ' ) . first ( )
if not leader :
logger . error ( " 未找到组长用户,无法进行自动对话 " )
return
# 如果是强制发送模式
if params . force_send :
if not params . content :
logger . error ( " 当force_send=true时, 必须提供content参数 " )
return
try :
# 获取知识库映射
mapping = GmailTalentMapping . objects . filter (
user = leader ,
talent_email = params . email ,
is_active = True
) . first ( )
if not mapping :
logger . error ( f " 找不到与邮箱 { params . email } 的映射关系 " )
return
# 创建Gmail集成实例
gmail_integration = GmailIntegration ( leader )
# 直接发送邮件
mail_subject = params . subject if params . subject else " 关于合作的洽谈 "
mail_result = gmail_integration . send_email (
to_email = params . email ,
subject = mail_subject ,
body = params . content ,
conversation_id = mapping . conversation_id
)
if mail_result [ ' status ' ] != ' success ' :
logger . error ( f " 邮件发送失败: { mail_result . get ( ' message ' , ' Unknown error ' ) } " )
return
# 保存发送的内容到对话历史
ChatHistory . objects . create (
user = leader ,
knowledge_base = mapping . knowledge_base ,
conversation_id = mapping . conversation_id ,
role = ' assistant ' ,
content = params . content
)
logger . info ( f " 已强制发送邮件到 { params . email } " )
return {
' status ' : ' success ' ,
' message ' : f " 已强制发送邮件到 { params . email } " ,
' email_sent ' : True ,
' conversation_id ' : mapping . conversation_id
}
except Exception as e :
logger . error ( f " 强制发送邮件时出错: { str ( e ) } " )
logger . error ( traceback . format_exc ( ) )
return {
' status ' : ' error ' ,
' message ' : str ( e )
}
# 执行自动对话
result = auto_chat_session ( leader , params . email )
# 打印结果
if result [ ' status ' ] == ' success ' :
if result . get ( ' email_sent ' ) :
logger . info ( f " 自动对话成功,已发送邮件到 { params . email } " )
else :
logger . info ( f " 没有新的达人回复,不需要发送邮件 " )
else :
logger . error ( f " 自动对话失败: { result . get ( ' message ' , ' Unknown error ' ) } " )
return result
def handle_set_goal ( args ) :
""" 处理设置目标命令 """
import argparse
parser = argparse . ArgumentParser ( description = ' 设置用户总目标 ' )
parser . add_argument ( ' --email ' , required = True , help = ' 达人邮箱 ' )
parser . add_argument ( ' --goal ' , required = True , help = ' 目标内容 ' )
params = parser . parse_args ( args )
# 获取组长用户
User = get_user_model ( )
leader = User . objects . filter ( role = ' leader ' ) . first ( )
if not leader :
logger . error ( " 未找到组长用户,无法设置目标 " )
return
# 设置总目标
gmail_integration = GmailIntegration ( leader )
result = gmail_integration . manage_user_goal ( params . goal )
# 打印结果
if result [ ' status ' ] == ' success ' :
logger . info ( f " 设置总目标成功: { result [ ' action ' ] } " )
else :
logger . error ( f " 设置总目标失败: { result . get ( ' message ' , ' Unknown error ' ) } " )
return result
def handle_check_goal ( args ) :
""" 处理检查目标完成状态命令 """
import argparse
parser = argparse . ArgumentParser ( description = ' 检查目标完成状态 ' )
parser . add_argument ( ' --email ' , required = True , help = ' 达人邮箱 ' )
params = parser . parse_args ( args )
# 获取组长用户
User = get_user_model ( )
leader = User . objects . filter ( role = ' leader ' ) . first ( )
if not leader :
logger . error ( " 未找到组长用户,无法检查目标 " )
return
# 查找Gmail映射关系
mapping = GmailTalentMapping . objects . filter (
user = leader ,
talent_email = params . email ,
is_active = True
) . first ( )
if not mapping :
logger . error ( f " 找不到与邮箱 { params . email } 的映射关系 " )
return {
' status ' : ' error ' ,
' message ' : f " 找不到与邮箱 { params . email } 的映射关系 "
}
# 获取对话历史中最后的AI回复
last_ai_message = ChatHistory . objects . filter (
user = leader ,
knowledge_base = mapping . knowledge_base ,
conversation_id = mapping . conversation_id ,
role = ' assistant ' ,
is_deleted = False
) . order_by ( ' -created_at ' ) . first ( )
if not last_ai_message :
logger . error ( f " 找不到与邮箱 { params . email } 的对话历史 " )
return {
' status ' : ' error ' ,
' message ' : f " 找不到与邮箱 { params . email } 的对话历史 "
}
# 检查目标是否已达成
goal_achieved = check_goal_achieved ( last_ai_message . content )
# 获取对话总结
summary = ConversationSummary . objects . filter (
user = leader ,
talent_email = params . email ,
is_active = True
) . order_by ( ' -updated_at ' ) . first ( )
result = {
' status ' : ' success ' ,
' email ' : params . email ,
' goal_achieved ' : goal_achieved ,
' last_message_time ' : last_ai_message . created_at . strftime ( ' % Y- % m- %d % H: % M: % S ' ) ,
' summary ' : summary . summary if summary else None
}
# 打印结果
logger . info ( f " 目标状态检查结果: " )
logger . info ( f " 邮箱: { params . email } " )
logger . info ( f " 目标达成: { goal_achieved } " )
logger . info ( f " 最后消息时间: { result [ ' last_message_time ' ] } " )
return result
2025-04-29 10:22:57 +08:00
def handle_export_excel ( args ) :
""" 处理导出Excel命令 """
import argparse
parser = argparse . ArgumentParser ( description = ' 导出匹配的邮箱数据到Excel ' )
parser . add_argument ( ' --app_token ' , default = " XYE6bMQUOaZ5y5svj4vcWohGnmg " , help = ' 飞书应用TOKEN ' )
parser . add_argument ( ' --table_id ' , required = True , help = ' 表格ID ' )
parser . add_argument ( ' --view_id ' , required = True , help = ' 视图ID ' )
parser . add_argument ( ' --access_token ' , default = None , help = ' 用户访问令牌 ' )
parser . add_argument ( ' --app_id ' , default = " cli_a5c97daacb9e500d " , help = ' 应用ID ' )
parser . add_argument ( ' --app_secret ' , default = " fdVeOCLXmuIHZVmSV0VbJh9wd0Kq1o5y " , help = ' 应用密钥 ' )
parser . add_argument ( ' --output_path ' , help = ' Excel导出文件路径 ' )
params = parser . parse_args ( args )
# 从飞书表格获取记录
records = fetch_table_records (
params . app_token ,
params . table_id ,
params . view_id ,
params . access_token ,
params . app_id ,
params . app_secret
)
if not records :
logger . error ( " 未获取到任何记录 " )
return
# 查找重复邮箱的创作者
duplicate_emails = find_duplicate_email_creators ( records )
if not duplicate_emails :
logger . info ( " 未找到与系统中已有creator匹配的邮箱 " )
return
logger . info ( f " 发现 { len ( duplicate_emails ) } 个匹配的邮箱,开始导出... " )
# 导出到Excel
output_path = export_matching_emails_to_excel ( duplicate_emails , records , params . output_path )
if output_path :
logger . info ( f " 已将匹配的邮箱数据导出到: { output_path } " )
else :
logger . error ( " 导出Excel失败 " )
def handle_export_creators ( args ) :
""" 处理导出FeishuCreator到Excel命令 """
import argparse
parser = argparse . ArgumentParser ( description = ' 导出匹配的FeishuCreator数据到Excel ' )
parser . add_argument ( ' --app_token ' , default = " XYE6bMQUOaZ5y5svj4vcWohGnmg " , help = ' 飞书应用TOKEN ' )
parser . add_argument ( ' --table_id ' , required = True , help = ' 表格ID ' )
parser . add_argument ( ' --view_id ' , required = True , help = ' 视图ID ' )
parser . add_argument ( ' --access_token ' , default = None , help = ' 用户访问令牌 ' )
parser . add_argument ( ' --app_id ' , default = " cli_a5c97daacb9e500d " , help = ' 应用ID ' )
parser . add_argument ( ' --app_secret ' , default = " fdVeOCLXmuIHZVmSV0VbJh9wd0Kq1o5y " , help = ' 应用密钥 ' )
parser . add_argument ( ' --output_path ' , help = ' Excel导出文件路径 ' )
params = parser . parse_args ( args )
# 从飞书表格获取记录
records = fetch_table_records (
params . app_token ,
params . table_id ,
params . view_id ,
params . access_token ,
params . app_id ,
params . app_secret
)
if not records :
logger . error ( " 未获取到任何记录 " )
return
# 查找重复邮箱的创作者
duplicate_emails = find_duplicate_email_creators ( records )
if not duplicate_emails :
logger . info ( " 未找到与系统中已有creator匹配的邮箱 " )
return
logger . info ( f " 发现 { len ( duplicate_emails ) } 个匹配的邮箱, 开始导出FeishuCreator数据... " )
# 导出到Excel
output_path = export_feishu_creators_to_excel ( duplicate_emails , params . output_path )
if output_path :
logger . info ( f " 已将匹配的FeishuCreator数据导出到: { output_path } " )
else :
logger . error ( " 导出FeishuCreator Excel失败 " )
def handle_process_notification ( args ) :
""" 处理Gmail通知命令 """
import argparse
import json
parser = argparse . ArgumentParser ( description = ' 处理Gmail通知 ' )
parser . add_argument ( ' --data ' , required = True , help = ' 通知数据JSON字符串或文件路径 ' )
parser . add_argument ( ' --user_email ' , help = ' 用户邮箱 ' )
params = parser . parse_args ( args )
# 获取通知数据
notification_data = None
try :
# 尝试解析为JSON字符串
notification_data = json . loads ( params . data )
except json . JSONDecodeError :
# 可能是文件路径
try :
with open ( params . data , ' r ' ) as f :
notification_data = json . load ( f )
except Exception as e :
logger . error ( f " 读取通知数据失败: { str ( e ) } " )
return
if not notification_data :
logger . error ( " 无法解析通知数据 " )
return
# 如果指定了用户邮箱,查找用户
user = None
if params . user_email :
from user_management . models import User
user = User . objects . filter ( email = params . user_email ) . first ( )
if not user :
logger . error ( f " 找不到邮箱为 { params . user_email } 的用户 " )
return
# 处理通知
result = process_gmail_notification ( notification_data , user )
# 打印结果
if result [ ' status ' ] == ' success ' :
logger . info ( f " 处理通知成功: { result [ ' message ' ] } " )
else :
logger . error ( f " 处理通知失败: { result [ ' message ' ] } " )
2025-04-17 16:14:00 +08:00
def main ( ) :
""" 命令行入口函数 """
import sys
if len ( sys . argv ) < 2 or sys . argv [ 1 ] == ' help ' :
print_help ( )
return
command = sys . argv [ 1 ]
args = sys . argv [ 2 : ]
if command == ' process_table ' :
handle_process_table ( args )
elif command == ' auto_chat ' :
handle_auto_chat ( args )
elif command == ' set_goal ' :
handle_set_goal ( args )
elif command == ' check_goal ' :
handle_check_goal ( args )
2025-04-29 10:22:57 +08:00
elif command == ' export_excel ' :
handle_export_excel ( args )
elif command == ' export_creators ' :
handle_export_creators ( args )
elif command == ' process_notification ' :
handle_process_notification ( args )
elif command == ' export_talent_replies ' :
handle_export_talent_replies ( args )
2025-04-17 16:14:00 +08:00
else :
print ( f " 未知命令: { command } " )
print_help ( )
if __name__ == " __main__ " :
2025-04-29 10:22:57 +08:00
main ( )
# Django API视图函数
def api_process_table ( request ) :
"""
Django API视图 : 处理飞书表格并设置Gmail自动回复
URL : / api / feishu / process - table /
方法 : POST
请求参数 :
{
" app_token " : " 飞书应用TOKEN " ,
" table_id " : " 表格ID " ,
" view_id " : " 视图ID " ,
" access_token " : " 访问令牌(可选) " ,
" app_id " : " 应用ID(可选) " ,
" app_secret " : " 应用密钥(可选) " ,
" goal_template " : " 目标内容模板(可选) " ,
" auto_reply " : true , / / 是否自动设置Gmail回复
" export_excel " : false , / / 是否导出匹配的邮箱数据到Excel
" export_creators " : false / / 是否导出匹配的FeishuCreator数据到Excel
}
响应 :
{
" status " : " success " ,
" message " : " 处理完成: 总计 X 个邮箱,成功 Y 个,失败 Z 个 " ,
" total " : 10 ,
" success " : 8 ,
" failure " : 2 ,
" auto_reply_setup " : 8 , / / 如果启用了自动回复
" details " : [ . . . ]
}
"""
try :
from django . http import JsonResponse
import json
# 1. 解析请求体
try :
if request . method == ' POST ' :
if request . content_type == ' application/json ' :
request_data = json . loads ( request . body )
else :
request_data = request . POST . dict ( )
else :
return JsonResponse ( {
' status ' : ' error ' ,
' message ' : f " 不支持的请求方法: { request . method } "
} , status = 405 )
except json . JSONDecodeError :
return JsonResponse ( {
' status ' : ' error ' ,
' message ' : " 无效的JSON格式 "
} , status = 400 )
# 2. 调用处理函数
results = process_table_api ( request_data )
# 3. 返回结果
status_code = 200 if results . get ( ' status ' ) == ' success ' else 400
return JsonResponse ( results , status = status_code )
except Exception as e :
logger . error ( f " API处理飞书表格出错: { str ( e ) } " )
logger . error ( traceback . format_exc ( ) )
return JsonResponse ( {
' status ' : ' error ' ,
' message ' : str ( e )
} , status = 500 )
def api_gmail_webhook ( request ) :
"""
Django API视图 : 处理Gmail推送通知
URL : / api / gmail / webhook /
方法 : POST
请求参数 : Google Pub / Sub推送格式
{
" message " : {
" data " : " BASE64编码的推送数据 " ,
" messageId " : " 消息ID " ,
" publishTime " : " 发布时间 "
} ,
" subscription " : " 订阅名称 "
}
响应 :
{
" status " : " success " ,
" message " : " 成功处理 X 条新消息 " ,
" processed " : 1
}
"""
try :
from django . http import JsonResponse
import json
# 1. 解析请求体
try :
if request . method == ' POST ' :
if request . content_type == ' application/json ' :
notification_data = json . loads ( request . body )
else :
return JsonResponse ( {
' status ' : ' error ' ,
' message ' : " 请求Content-Type必须为application/json "
} , status = 400 )
else :
return JsonResponse ( {
' status ' : ' error ' ,
' message ' : f " 不支持的请求方法: { request . method } "
} , status = 405 )
except json . JSONDecodeError :
return JsonResponse ( {
' status ' : ' error ' ,
' message ' : " 无效的JSON格式 "
} , status = 400 )
# 2. 调用处理函数
result = process_gmail_notification ( notification_data )
# 3. 返回结果
status_code = 200 if result . get ( ' status ' ) == ' success ' else 400
return JsonResponse ( result , status = status_code )
except Exception as e :
logger . error ( f " API处理Gmail通知出错: { str ( e ) } " )
logger . error ( traceback . format_exc ( ) )
return JsonResponse ( {
' status ' : ' error ' ,
' message ' : str ( e )
} , status = 500 )
def api_auto_chat ( request ) :
"""
Django API视图 : 初始化自动对话
URL : / api / auto_chat /
方法 : POST
请求参数 :
{
" talent_email " : " 达人邮箱 " ,
" max_turns " : 10 / / 可选 , 最大对话轮次
}
响应 :
{
" status " : " success " ,
" message " : " 已发送首次邮件并设置Gmail监听, 等待达人回复 " ,
" turns_completed " : 1 ,
" goal_achieved " : false ,
" email_sent " : true ,
" conversation_id " : " 会话ID "
}
"""
try :
from django . http import JsonResponse
import json
from django . contrib . auth import get_user_model
# 1. 解析请求体
try :
if request . method == ' POST ' :
if request . content_type == ' application/json ' :
request_data = json . loads ( request . body )
else :
request_data = request . POST . dict ( )
else :
return JsonResponse ( {
' status ' : ' error ' ,
' message ' : f " 不支持的请求方法: { request . method } "
} , status = 405 )
except json . JSONDecodeError :
return JsonResponse ( {
' status ' : ' error ' ,
' message ' : " 无效的JSON格式 "
} , status = 400 )
# 2. 获取必要参数
talent_email = request_data . get ( ' talent_email ' )
if not talent_email :
return JsonResponse ( {
' status ' : ' error ' ,
' message ' : " 缺少必要参数: talent_email "
} , status = 400 )
max_turns = request_data . get ( ' max_turns ' , 10 )
# 3. 获取用户 - 优先使用当前登录用户,否则使用组长用户
user = request . user if hasattr ( request , ' user ' ) and request . user . is_authenticated else None
if not user :
User = get_user_model ( )
user = User . objects . filter ( role = ' leader ' ) . first ( )
if not user :
return JsonResponse ( {
' status ' : ' error ' ,
' message ' : " 未找到可用用户,请先登录或确保系统中有组长用户 "
} , status = 400 )
# 4. 执行自动对话
result = auto_chat_session ( user , talent_email , max_turns = max_turns )
# 5. 返回结果
status_code = 200 if result . get ( ' status ' ) == ' success ' else 400
return JsonResponse ( result , status = status_code )
except Exception as e :
logger . error ( f " API初始化自动对话出错: { str ( e ) } " )
logger . error ( traceback . format_exc ( ) )
return JsonResponse ( {
' status ' : ' error ' ,
' message ' : str ( e )
} , status = 500 )
def export_talent_replies_to_excel ( user = None , output_path = None ) :
"""
导出达人回复数据到Excel文件
参数 :
user : 用户对象 , 如果为None则导出所有用户的数据
output_path : 输出文件路径 , 默认为当前目录下的 ' talent_replies_ {timestamp} .xlsx '
返回 :
str : 导出文件的路径
"""
try :
# 导入需要的模块
import pandas as pd
from datetime import datetime
from django . db . models import Q , F
from user_management . models import ChatHistory , GmailTalentMapping , UserGoal , User
# 如果未指定输出路径,创建默认路径
if not output_path :
timestamp = datetime . now ( ) . strftime ( ' % Y % m %d _ % H % M % S ' )
output_path = f ' talent_replies_ { timestamp } .xlsx '
# 准备查询 - 查询达人发送的消息, role='assistant'表示达人回复
query = Q ( role = ' assistant ' ) & Q ( is_deleted = False )
# 如果指定了用户,只查询该用户相关的数据
if user :
query & = Q ( user = user )
logger . info ( f " 导出用户 { user . email } 收到的所有达人回复数据 " )
else :
logger . info ( " 导出所有用户收到的达人回复数据 " )
# 获取所有达人回复消息
talent_replies = ChatHistory . objects . filter ( query ) . order_by ( ' conversation_id ' , ' created_at ' )
if not talent_replies . exists ( ) :
logger . warning ( " 没有找到任何达人回复记录 " )
return None
# 创建数据列表
data = [ ]
# 获取所有相关的映射关系
conversation_ids = talent_replies . values_list ( ' conversation_id ' , flat = True ) . distinct ( )
talent_mappings = GmailTalentMapping . objects . filter ( conversation_id__in = conversation_ids )
# 创建映射字典,用于快速查找
mapping_dict = { }
for mapping in talent_mappings :
mapping_dict [ mapping . conversation_id ] = mapping . talent_email
# 获取所有用户的目标
user_ids = talent_replies . values_list ( ' user_id ' , flat = True ) . distinct ( )
goals = UserGoal . objects . filter ( user_id__in = user_ids , is_active = True )
# 创建目标字典,用于快速查找
goal_dict = { }
for goal in goals :
goal_dict [ goal . user_id ] = goal . content
# 获取所有相关用户
users = User . objects . filter ( id__in = user_ids )
user_dict = { }
for u in users :
user_dict [ u . id ] = u . email
# 遍历达人回复
for reply in talent_replies :
# 获取回复的父消息(可能是用户消息)
parent_message = None
if reply . parent_id :
try :
# 使用UUID查询方式, 避免ID格式转换问题
parent_message = ChatHistory . objects . filter ( conversation_id = reply . conversation_id ) . filter ( id = reply . parent_id ) . first ( )
# 如果没找到, 可能是字符串ID
if not parent_message :
logger . warning ( f " 无法直接通过ID查找父消息, ID: { reply . parent_id } , 尝试其他方式查找... " )
except Exception as e :
logger . error ( f " 查询父消息时出错: { str ( e ) } " )
logger . error ( traceback . format_exc ( ) )
# 从metadata获取额外信息
metadata = reply . metadata or { }
# 准备行数据
row = {
' ID ' : str ( reply . id ) ,
' 用户邮箱 ' : user_dict . get ( reply . user_id , ' 未知 ' ) ,
' 达人邮箱 ' : mapping_dict . get ( reply . conversation_id , ' 未知 ' ) ,
' 对话ID ' : reply . conversation_id ,
' 内容 ' : reply . content ,
' 创建时间 ' : reply . created_at . strftime ( ' % Y- % m- %d % H: % M: % S ' ) ,
' 父消息ID ' : reply . parent_id ,
' 父消息内容 ' : parent_message . content if parent_message else ' ' ,
' 父消息时间 ' : parent_message . created_at . strftime ( ' % Y- % m- %d % H: % M: % S ' ) if parent_message else ' ' ,
' 用户总目标 ' : goal_dict . get ( reply . user_id , ' 未设置 ' ) ,
' 邮件主题 ' : metadata . get ( ' subject ' , ' ' ) ,
' 发件人 ' : metadata . get ( ' from ' , ' ' ) ,
' 邮件日期 ' : metadata . get ( ' date ' , ' ' ) ,
' Gmail消息ID ' : metadata . get ( ' gmail_message_id ' , ' ' )
}
data . append ( row )
# 创建DataFrame
df = pd . DataFrame ( data )
# 导出到Excel
try :
# 首先尝试使用openpyxl
df . to_excel ( output_path , index = False , engine = ' openpyxl ' )
except Exception as e :
logger . warning ( f " 使用openpyxl导出失败, 尝试使用默认引擎: { e } " )
# 如果openpyxl不可用, 使用默认引擎
df . to_excel ( output_path , index = False )
logger . info ( f " 成功导出 { len ( data ) } 条达人回复记录到: { output_path } " )
return output_path
except Exception as e :
logger . error ( f " 导出达人回复数据到Excel时出错: { str ( e ) } " )
logger . error ( traceback . format_exc ( ) )
return None
def handle_export_talent_replies ( args ) :
""" 处理导出达人回复数据命令 """
import argparse
parser = argparse . ArgumentParser ( description = ' 导出达人回复数据到Excel ' )
parser . add_argument ( ' --user_email ' , help = ' 用户邮箱,不指定则导出所有用户数据 ' )
parser . add_argument ( ' --output_path ' , help = ' Excel导出文件路径 ' )
params = parser . parse_args ( args )
# 如果指定了用户邮箱,查找用户
user = None
if params . user_email :
from django . contrib . auth import get_user_model
User = get_user_model ( )
user = User . objects . filter ( email = params . user_email ) . first ( )
if not user :
logger . error ( f " 找不到邮箱为 { params . user_email } 的用户 " )
return
# 导出数据
output_path = export_talent_replies_to_excel ( user , params . output_path )
if output_path :
logger . info ( f " 已将达人回复数据导出到: { output_path } " )
else :
logger . error ( " 导出达人回复数据失败 " )
def api_export_talent_replies ( request ) :
"""
Django API视图 : 导出达人回复数据到Excel
URL : / api / users / talent - replies / export /
方法 : GET , POST
请求参数 :
{
" user_email " : " 可选,指定用户邮箱 " ,
" output_path " : " 可选,指定输出文件路径 "
}
响应 :
{
" status " : " success " ,
" message " : " 已导出达人回复数据 " ,
" file_path " : " 导出文件路径 " ,
" records_count " : 123
}
注册URL :
在urls . py中添加 :
from feishu . feishu_ai_chat import api_export_talent_replies
path ( ' api/users/talent-replies/export/ ' , api_export_talent_replies , name = ' export_talent_replies ' )
"""
try :
from django . http import JsonResponse , FileResponse
import json
import os
# 解析请求参数
if request . method == ' POST ' :
if request . content_type == ' application/json ' :
params = json . loads ( request . body )
else :
params = request . POST . dict ( )
else :
params = request . GET . dict ( )
# 获取用户邮箱参数
user_email = params . get ( ' user_email ' )
output_path = params . get ( ' output_path ' )
# 如果指定了用户邮箱,查找用户
user = None
if user_email :
from django . contrib . auth import get_user_model
User = get_user_model ( )
user = User . objects . filter ( email = user_email ) . first ( )
if not user :
return JsonResponse ( {
' status ' : ' error ' ,
' message ' : f " 找不到邮箱为 { user_email } 的用户 "
} , status = 400 )
# 导出数据
file_path = export_talent_replies_to_excel ( user , output_path )
if not file_path :
return JsonResponse ( {
' status ' : ' error ' ,
' message ' : " 导出达人回复数据失败 "
} , status = 500 )
# 获取导出的记录数量
try :
import pandas as pd
df = pd . read_excel ( file_path )
records_count = len ( df )
except Exception as e :
logger . error ( f " 读取导出文件失败: { str ( e ) } " )
records_count = 0
# 如果客户端要求直接下载文件
if params . get ( ' download ' ) == ' true ' and os . path . exists ( file_path ) :
response = FileResponse ( open ( file_path , ' rb ' ) )
response [ ' Content-Disposition ' ] = f ' attachment; filename= " { os . path . basename ( file_path ) } " '
return response
# 计算一些统计数据
stats = {
' records_count ' : records_count ,
' talents_count ' : 0 ,
' conversations_count ' : 0 ,
' date_range ' : {
' start ' : ' ' ,
' end ' : ' '
}
}
try :
if records_count > 0 :
# 统计达人数量
stats [ ' talents_count ' ] = df [ ' 达人邮箱 ' ] . nunique ( )
# 统计对话数量
stats [ ' conversations_count ' ] = df [ ' 对话ID ' ] . nunique ( )
# 统计日期范围
if ' 创建时间 ' in df . columns :
df [ ' 创建时间 ' ] = pd . to_datetime ( df [ ' 创建时间 ' ] )
min_date = df [ ' 创建时间 ' ] . min ( )
max_date = df [ ' 创建时间 ' ] . max ( )
if not pd . isna ( min_date ) and not pd . isna ( max_date ) :
stats [ ' date_range ' ] [ ' start ' ] = min_date . strftime ( ' % Y- % m- %d ' )
stats [ ' date_range ' ] [ ' end ' ] = max_date . strftime ( ' % Y- % m- %d ' )
except Exception as stats_error :
logger . error ( f " 计算统计数据失败: { stats_error } " )
# 否则返回文件信息
return JsonResponse ( {
' status ' : ' success ' ,
' message ' : " 已导出达人回复数据 " ,
' file_path ' : file_path ,
' records_count ' : records_count ,
' stats ' : stats ,
' download_url ' : f " /api/users/talent-replies/download/?path= { file_path } "
} )
except Exception as e :
logger . error ( f " API导出达人回复数据出错: { str ( e ) } " )
logger . error ( traceback . format_exc ( ) )
return JsonResponse ( {
' status ' : ' error ' ,
' message ' : str ( e )
} , status = 500 )