1. 调整导入顺序与补全缺失依赖 2. 新增Zalo目标前缀映射与最大重连延迟配置 3. 优化反应消息ID获取逻辑与桥接URL检查 4. 改进WebSocket重连退避策略与错误处理 5. 重构发送消息逻辑,统一附件处理与音频发送流程 6. 调整类初始化代码位置与导入依赖顺序
201 lines
4.9 KiB
Python
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))
|