feat: 重构订单和物流信息展示格式
主要改动: - 订单列表:使用 order_list 格式,展示 5 个订单(全部状态) - 订单详情:使用 order_detail 格式,优化价格和时间显示 - 物流信息:使用 logistics 格式,根据 track id 动态生成步骤 - 商品图片:从 orderProduct.imageUrl 字段获取 - 时间格式:统一为 YYYY-MM-DD HH:MM:SS - 多语言支持:amountLabel、orderTime 支持中英文 - 配置管理:新增 FRONTEND_URL 环境变量 - API 集成:改进 Mall API tracks 数据解析 - 认证优化:account_id 从 webhook 动态获取 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,10 @@ class WebhookSender(BaseModel):
|
||||
name: Optional[str] = None
|
||||
email: Optional[str] = None
|
||||
type: Optional[str] = None # "contact" or "user"
|
||||
identifier: Optional[str] = None
|
||||
jwt_token: Optional[str] = None # JWT token at sender root level
|
||||
mall_token: Optional[str] = None # Mall token at sender root level
|
||||
custom_attributes: Optional[dict] = None # May also contain tokens
|
||||
|
||||
|
||||
class WebhookMessage(BaseModel):
|
||||
@@ -156,13 +160,22 @@ async def handle_incoming_message(payload: ChatwootWebhookPayload, cookie_token:
|
||||
sender_keys=list(payload_dict.get('sender', {}).keys()) if payload_dict.get('sender') else []
|
||||
)
|
||||
|
||||
# 打印完整的 payload 内容用于调试
|
||||
import json
|
||||
logger.info(
|
||||
"Full webhook payload JSON",
|
||||
payload_json=json.dumps(payload_dict, indent=2, ensure_ascii=False, default=str)
|
||||
)
|
||||
|
||||
# Get account_id from payload (top-level account object)
|
||||
# Chatwoot webhook includes account info at the top level
|
||||
account_obj = payload.account
|
||||
# 从 webhook 中动态获取 account_id
|
||||
account_id = str(account_obj.get("id")) if account_obj else "1"
|
||||
|
||||
# 优先使用 Cookie 中的 token
|
||||
user_token = cookie_token
|
||||
mall_token = None
|
||||
|
||||
# 如果 Cookie 中没有,尝试从多个来源提取 token
|
||||
if not user_token:
|
||||
@@ -173,14 +186,14 @@ async def handle_incoming_message(payload: ChatwootWebhookPayload, cookie_token:
|
||||
contact_id=contact.id if contact else None,
|
||||
contact_type=type(contact).__name__
|
||||
)
|
||||
|
||||
|
||||
# 只有 WebhookContact 才有 custom_attributes
|
||||
if hasattr(contact, 'custom_attributes'):
|
||||
logger.info(
|
||||
"Checking contact custom_attributes",
|
||||
has_custom_attributes=bool(contact.custom_attributes)
|
||||
)
|
||||
|
||||
|
||||
custom_attrs = contact.custom_attributes or {}
|
||||
logger.info(
|
||||
"Contact custom_attributes",
|
||||
@@ -188,15 +201,25 @@ async def handle_incoming_message(payload: ChatwootWebhookPayload, cookie_token:
|
||||
has_jwt_token='jwt_token' in custom_attrs if custom_attrs else False,
|
||||
has_mall_token='mall_token' in custom_attrs if custom_attrs else False
|
||||
)
|
||||
|
||||
contact_dict = {"custom_attributes": custom_attrs}
|
||||
user_token = TokenManager.extract_token_from_contact(contact_dict)
|
||||
logger.debug("Extracted token from contact", has_token=bool(user_token))
|
||||
|
||||
# 同时提取 jwt_token 和 mall_token
|
||||
if custom_attrs.get('jwt_token'):
|
||||
user_token = custom_attrs.get('jwt_token')
|
||||
logger.info("JWT token found in contact.custom_attributes", token_prefix=user_token[:20] if user_token else None)
|
||||
if custom_attrs.get('mall_token'):
|
||||
mall_token = custom_attrs.get('mall_token')
|
||||
logger.info("Mall token found in contact.custom_attributes", token_prefix=mall_token[:20] if mall_token else None)
|
||||
|
||||
# 如果没有找到 token,尝试使用通用字段
|
||||
if not user_token and not mall_token:
|
||||
contact_dict = {"custom_attributes": custom_attrs}
|
||||
user_token = TokenManager.extract_token_from_contact(contact_dict)
|
||||
logger.debug("Extracted token from contact (generic)", has_token=bool(user_token))
|
||||
else:
|
||||
logger.debug("Contact type is WebhookSender, no custom_attributes available")
|
||||
|
||||
# 2. 尝试从 conversation.meta.sender.custom_attributes 获取(Chatwoot SDK setUser 设置的位置)
|
||||
if not user_token and conversation:
|
||||
# 2. 尝试从 conversation.meta.sender 获取(Chatwoot SDK setUser 设置的位置)
|
||||
if (not user_token or not mall_token) and conversation:
|
||||
logger.debug("Conversation object type", type=str(type(conversation)))
|
||||
if hasattr(conversation, 'model_dump'):
|
||||
conv_dict = conversation.model_dump()
|
||||
@@ -210,11 +233,30 @@ async def handle_incoming_message(payload: ChatwootWebhookPayload, cookie_token:
|
||||
sender_keys=list(meta_sender.keys()) if meta_sender else [],
|
||||
has_custom_attributes=bool(meta_sender.get('custom_attributes')) if meta_sender else False
|
||||
)
|
||||
|
||||
if meta_sender.get('custom_attributes'):
|
||||
|
||||
# 2.1. 优先从 meta.sender 根级别获取 token
|
||||
if not user_token and meta_sender.get('jwt_token'):
|
||||
user_token = meta_sender.get('jwt_token')
|
||||
logger.info("JWT token found in conversation.meta.sender (root level)", token_prefix=user_token[:20] if user_token else None)
|
||||
if not mall_token and meta_sender.get('mall_token'):
|
||||
mall_token = meta_sender.get('mall_token')
|
||||
logger.info("Mall token found in conversation.meta.sender (root level)", token_prefix=mall_token[:20] if mall_token else None)
|
||||
|
||||
# 2.2. 其次从 meta.sender.custom_attributes 获取
|
||||
if (not user_token or not mall_token) and meta_sender.get('custom_attributes'):
|
||||
logger.info("Found custom_attributes in meta.sender", keys=list(meta_sender['custom_attributes'].keys()))
|
||||
user_token = TokenManager.extract_token_from_contact({'custom_attributes': meta_sender['custom_attributes']})
|
||||
logger.info("Token found in conversation.meta.sender.custom_attributes", token_prefix=user_token[:20] if user_token else None)
|
||||
custom_attrs = meta_sender['custom_attributes']
|
||||
if not user_token and custom_attrs.get('jwt_token'):
|
||||
user_token = custom_attrs.get('jwt_token')
|
||||
logger.info("JWT token found in conversation.meta.sender.custom_attributes", token_prefix=user_token[:20] if user_token else None)
|
||||
if not mall_token and custom_attrs.get('mall_token'):
|
||||
mall_token = custom_attrs.get('mall_token')
|
||||
logger.info("Mall token found in conversation.meta.sender.custom_attributes", token_prefix=mall_token[:20] if mall_token else None)
|
||||
|
||||
# 如果只有 jwt_token,将它也用作 mall_token
|
||||
if user_token and not mall_token:
|
||||
mall_token = user_token
|
||||
logger.debug("Using jwt_token as mall_token", token_prefix=mall_token[:20] if mall_token else None)
|
||||
|
||||
if user_token:
|
||||
logger.info(
|
||||
@@ -236,9 +278,22 @@ async def handle_incoming_message(payload: ChatwootWebhookPayload, cookie_token:
|
||||
conversation_id=conversation_id,
|
||||
user_id=user_id,
|
||||
has_token=bool(user_token),
|
||||
message_length=len(content)
|
||||
message_length=len(content),
|
||||
channel=conversation.channel if conversation else None
|
||||
)
|
||||
|
||||
# 识别消息渠道(邮件、网站等)
|
||||
message_channel = conversation.channel if conversation else "Channel"
|
||||
is_email = message_channel == "Email"
|
||||
|
||||
# 邮件渠道特殊处理
|
||||
if is_email:
|
||||
logger.info(
|
||||
"Email channel detected",
|
||||
conversation_id=conversation_id,
|
||||
sender_email=contact.email if contact else None
|
||||
)
|
||||
|
||||
# Load conversation context from cache
|
||||
cache = get_cache_manager()
|
||||
await cache.connect()
|
||||
@@ -249,6 +304,12 @@ async def handle_incoming_message(payload: ChatwootWebhookPayload, cookie_token:
|
||||
# Add token to context if available
|
||||
if user_token:
|
||||
context["user_token"] = user_token
|
||||
if mall_token:
|
||||
context["mall_token"] = mall_token
|
||||
|
||||
# 添加渠道信息到 context(让 Agent 知道是邮件还是网站)
|
||||
context["channel"] = message_channel
|
||||
context["is_email"] = is_email
|
||||
|
||||
try:
|
||||
# Process message through agent workflow
|
||||
@@ -259,24 +320,26 @@ async def handle_incoming_message(payload: ChatwootWebhookPayload, cookie_token:
|
||||
message=content,
|
||||
history=history,
|
||||
context=context,
|
||||
user_token=user_token
|
||||
user_token=user_token,
|
||||
mall_token=mall_token
|
||||
)
|
||||
|
||||
# Get response
|
||||
response = final_state.get("response")
|
||||
if not response:
|
||||
if response is None:
|
||||
response = "抱歉,我暂时无法处理您的请求。请稍后重试或联系人工客服。"
|
||||
|
||||
# Send response to Chatwoot
|
||||
# Create client with correct account_id from webhook
|
||||
|
||||
# Create Chatwoot client
|
||||
from integrations.chatwoot import ChatwootClient
|
||||
chatwoot = ChatwootClient(account_id=int(account_id))
|
||||
await chatwoot.send_message(
|
||||
conversation_id=conversation.id,
|
||||
content=response
|
||||
)
|
||||
await chatwoot.close()
|
||||
|
||||
|
||||
# Send response to Chatwoot (skip if empty - agent may have already sent rich content)
|
||||
if response:
|
||||
await chatwoot.send_message(
|
||||
conversation_id=conversation.id,
|
||||
content=response
|
||||
)
|
||||
|
||||
# Handle human handoff
|
||||
if final_state.get("requires_human"):
|
||||
await chatwoot.update_conversation_status(
|
||||
@@ -288,6 +351,8 @@ async def handle_incoming_message(payload: ChatwootWebhookPayload, cookie_token:
|
||||
conversation_id=conversation.id,
|
||||
labels=["needs_human", final_state.get("intent", "unknown")]
|
||||
)
|
||||
|
||||
await chatwoot.close()
|
||||
|
||||
# Update cache
|
||||
await cache.add_message(conversation_id, "user", content)
|
||||
@@ -313,12 +378,12 @@ async def handle_incoming_message(payload: ChatwootWebhookPayload, cookie_token:
|
||||
)
|
||||
|
||||
# Send error response
|
||||
chatwoot = get_chatwoot_client()
|
||||
chatwoot = get_chatwoot_client(account_id=int(account_id))
|
||||
await chatwoot.send_message(
|
||||
conversation_id=conversation.id,
|
||||
content="抱歉,处理您的消息时遇到了问题。我们的客服团队将尽快为您服务。"
|
||||
)
|
||||
|
||||
|
||||
# Transfer to human
|
||||
await chatwoot.update_conversation_status(
|
||||
conversation_id=conversation.id,
|
||||
@@ -328,30 +393,36 @@ async def handle_incoming_message(payload: ChatwootWebhookPayload, cookie_token:
|
||||
|
||||
async def handle_conversation_created(payload: ChatwootWebhookPayload) -> None:
|
||||
"""Handle new conversation created
|
||||
|
||||
|
||||
Args:
|
||||
payload: Webhook payload
|
||||
"""
|
||||
conversation = payload.conversation
|
||||
if not conversation:
|
||||
return
|
||||
|
||||
|
||||
conversation_id = str(conversation.id)
|
||||
|
||||
|
||||
# Get account_id from payload
|
||||
account_obj = payload.account
|
||||
# 从 webhook 中动态获取 account_id
|
||||
account_id = str(account_obj.get("id")) if account_obj else "1"
|
||||
|
||||
logger.info(
|
||||
"New conversation created",
|
||||
conversation_id=conversation_id
|
||||
conversation_id=conversation_id,
|
||||
account_id=account_id
|
||||
)
|
||||
|
||||
|
||||
# Initialize conversation context
|
||||
cache = get_cache_manager()
|
||||
await cache.connect()
|
||||
|
||||
|
||||
context = {
|
||||
"created": True,
|
||||
"inbox_id": conversation.inbox_id
|
||||
}
|
||||
|
||||
|
||||
# Add contact info to context
|
||||
contact = payload.contact
|
||||
if contact:
|
||||
@@ -359,15 +430,24 @@ async def handle_conversation_created(payload: ChatwootWebhookPayload) -> None:
|
||||
context["contact_email"] = contact.email
|
||||
if contact.custom_attributes:
|
||||
context.update(contact.custom_attributes)
|
||||
|
||||
|
||||
await cache.set_context(conversation_id, context)
|
||||
|
||||
# Send welcome message
|
||||
chatwoot = get_chatwoot_client()
|
||||
await chatwoot.send_message(
|
||||
conversation_id=conversation.id,
|
||||
content="您好!我是 AI 智能助手,很高兴为您服务。请问有什么可以帮您的?\n\n您可以询问我关于订单、商品、售后等问题,我会尽力为您解答。"
|
||||
)
|
||||
|
||||
# 检查是否是邮件渠道
|
||||
is_email = conversation.channel == "Email" if conversation else False
|
||||
|
||||
# 只对非邮件渠道发送欢迎消息
|
||||
if not is_email:
|
||||
chatwoot = get_chatwoot_client(account_id=int(account_id))
|
||||
await chatwoot.send_message(
|
||||
conversation_id=conversation.id,
|
||||
content="您好!我是 AI 智能助手,很高兴为您服务。请问有什么可以帮您的?\n\n您可以询问我关于订单、商品、售后等问题,我会尽力为您解答。"
|
||||
)
|
||||
else:
|
||||
logger.info(
|
||||
"Email channel detected, skipping welcome message",
|
||||
conversation_id=conversation_id
|
||||
)
|
||||
|
||||
|
||||
async def handle_conversation_status_changed(payload: ChatwootWebhookPayload) -> None:
|
||||
|
||||
Reference in New Issue
Block a user