ForcePilot/backend/package/yuxi/channels/adapters/zalo_user/cards.py
Kris 6cb56d5e41 refactor(zalo-adapter): 整理Zalo用户适配器代码并优化重连逻辑
1. 调整导入顺序与补全缺失依赖
2. 新增Zalo目标前缀映射与最大重连延迟配置
3. 优化反应消息ID获取逻辑与桥接URL检查
4. 改进WebSocket重连退避策略与错误处理
5. 重构发送消息逻辑,统一附件处理与音频发送流程
6. 调整类初始化代码位置与导入依赖顺序
2026-05-13 16:18:25 +08:00

201 lines
4.9 KiB
Python

from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any
from .inline_buttons import ButtonGroup
@dataclass
class CardImage:
url: str
aspect_ratio: str = "1:1"
def to_dict(self) -> dict[str, str]:
return {"url": self.url, "aspect_ratio": self.aspect_ratio}
@dataclass
class CardAction:
url: str | None = None
callback_data: str | None = None
def to_dict(self) -> dict[str, Any]:
d: dict[str, Any] = {}
if self.url:
d["url"] = self.url
if self.callback_data:
d["callback_data"] = self.callback_data
return d
@dataclass
class ZaloCard:
title: str = ""
description: str = ""
image: CardImage | None = None
buttons: ButtonGroup | None = None
action: CardAction | None = None
def to_dict(self) -> dict[str, Any]:
d: dict[str, Any] = {
"type": "card",
}
if self.title:
d["title"] = self.title
if self.description:
d["description"] = self.description
if self.image:
d["image"] = self.image.to_dict()
if self.buttons:
d["buttons"] = self.buttons.to_dict()
if self.action:
d["action"] = self.action.to_dict()
return d
@dataclass
class CardMessage:
cards: list[ZaloCard] = field(default_factory=list)
layout: str = "horizontal" # "horizontal", "carousel", "vertical"
def to_dict(self) -> dict[str, Any]:
return {
"type": "card_list",
"layout": self.layout,
"elements": [c.to_dict() for c in self.cards],
}
def build_card_carousel(
conversation_id: str,
cards: list[ZaloCard],
) -> dict[str, Any]:
return build_card_payload(conversation_id, CardMessage(cards=cards, layout="carousel"))
def build_card_payload(
conversation_id: str,
cards: CardMessage,
) -> dict[str, Any]:
return {
"conversation_id": conversation_id,
"message_type": "card",
"cards": cards.to_dict(),
}
def simple_card(
title: str,
description: str = "",
image_url: str | None = None,
action_url: str | None = None,
) -> ZaloCard:
card = ZaloCard(title=title, description=description)
if image_url:
card.image = CardImage(url=image_url)
if action_url:
card.action = CardAction(url=action_url)
return card
def template_welcome_card(bot_name: str, description: str = "") -> ZaloCard:
return ZaloCard(
title=f"Welcome to {bot_name}",
description=description or "I'm an AI assistant. How can I help you today?",
)
def template_confirmation_card(
title: str,
confirm_label: str = "Confirm",
cancel_label: str = "Cancel",
confirm_data: str = "confirm",
cancel_data: str = "cancel",
) -> ZaloCard:
from .inline_buttons import (
ButtonGroup,
ButtonStyle,
InlineButtonRow,
callback_button,
)
card = ZaloCard(
title=title,
description="Please confirm your action.",
buttons=ButtonGroup(
rows=[
InlineButtonRow(
buttons=[
callback_button(confirm_label, confirm_data, ButtonStyle.PRIMARY),
callback_button(cancel_label, cancel_data, ButtonStyle.DEFAULT),
]
)
]
),
)
return card
def template_info_card(
title: str,
fields: dict[str, str],
image_url: str | None = None,
) -> ZaloCard:
description_lines = [f"**{k}**: {v}" for k, v in fields.items()]
card = ZaloCard(
title=title,
description="\n".join(description_lines),
)
if image_url:
card.image = CardImage(url=image_url)
return card
def template_product_card(
product_name: str,
price: str,
description: str = "",
image_url: str | None = None,
buy_url: str | None = None,
) -> ZaloCard:
from .inline_buttons import (
ButtonGroup,
ButtonStyle,
InlineButtonRow,
url_button,
)
card = ZaloCard(
title=product_name,
description=description or f"Price: {price}",
)
if image_url:
card.image = CardImage(url=image_url)
if buy_url:
card.buttons = ButtonGroup(
rows=[
InlineButtonRow(
buttons=[
url_button("Buy Now", buy_url, ButtonStyle.PRIMARY),
]
)
]
)
return card
async def send_card(
bridge: Any,
conversation_id: str,
card: ZaloCard,
):
from yuxi.channels.models import DeliveryResult
card_msg = CardMessage(cards=[card])
payload = build_card_payload(conversation_id, card_msg)
try:
return await bridge.send_message(payload)
except Exception as e:
return DeliveryResult(success=False, error=str(e))