ForcePilot/backend/package/yuxi/channels/adapters/msteams/cards.py
Kris bd60c15df0 feat(msteams): 新增完整的 Microsoft Teams 适配器模块
实现了 Teams 机器人所需的全功能组件,包括:
- 基础命令解析与帮助卡片生成
- 租户验证与访问控制
- 自定义 UA 与媒体工具
- 消息分块、批注处理与会话管理
- 防抖、缓存与配置路由能力
- 投票、配对、审计与运行时状态管理
- TTS 语音合成与卡片构建工具
- 群组管理与权限控制逻辑
2026-05-12 00:46:44 +08:00

148 lines
4.0 KiB
Python

"""Microsoft Teams Adaptive Card 构建器。
提供常用交互卡片的构建函数,返回 Adaptive Card JSON schema (v1.5)。
"""
from __future__ import annotations
from typing import Any
def build_vote_card(
title: str,
options: list[str],
callback_data: dict[str, Any] | None = None,
max_selections: int | None = None,
) -> dict[str, Any]:
choices = [{"title": opt, "value": opt} for opt in options[:12]]
allow_multi = max_selections is not None and max_selections > 1
choice_set: dict[str, Any] = {
"type": "Input.ChoiceSet",
"id": "voteOption",
"style": "expanded",
"isMultiSelect": allow_multi,
"choices": choices,
}
if allow_multi:
choice_set["maxSelections"] = max_selections
return {
"type": "AdaptiveCard",
"version": "1.5",
"body": [
{"type": "TextBlock", "size": "Large", "weight": "Bolder", "text": title},
choice_set,
],
"actions": [
{
"type": "Action.Submit",
"title": "投票",
"data": callback_data or {},
}
],
}
def build_confirm_card(
title: str,
message: str,
confirm_text: str = "确认",
cancel_text: str = "取消",
confirm_data: dict[str, Any] | None = None,
cancel_data: dict[str, Any] | None = None,
) -> dict[str, Any]:
return {
"type": "AdaptiveCard",
"version": "1.5",
"body": [
{"type": "TextBlock", "size": "Large", "weight": "Bolder", "text": title},
{"type": "TextBlock", "text": message, "wrap": True},
],
"actions": [
{
"type": "Action.Submit",
"title": confirm_text,
"style": "positive",
"data": confirm_data or {"action": "confirm"},
},
{
"type": "Action.Submit",
"title": cancel_text,
"style": "default",
"data": cancel_data or {"action": "cancel"},
},
],
}
def build_info_card(
title: str,
facts: dict[str, str],
subtitle: str | None = None,
) -> dict[str, Any]:
body: list[dict[str, Any]] = [
{"type": "TextBlock", "size": "Large", "weight": "Bolder", "text": title},
]
if subtitle:
body.append({"type": "TextBlock", "text": subtitle, "isSubtle": True, "wrap": True})
body.append(
{
"type": "FactSet",
"facts": [{"title": k, "value": v} for k, v in facts.items()],
}
)
return {"type": "AdaptiveCard", "version": "1.5", "body": body}
def build_input_card(
title: str,
fields: list[dict[str, Any]],
submit_text: str = "提交",
callback_data: dict[str, Any] | None = None,
) -> dict[str, Any]:
body: list[dict[str, Any]] = [
{"type": "TextBlock", "size": "Large", "weight": "Bolder", "text": title},
]
for field in fields:
body.append(field)
return {
"type": "AdaptiveCard",
"version": "1.5",
"body": body,
"actions": [
{
"type": "Action.Submit",
"title": submit_text,
"data": callback_data or {},
}
],
}
def wrap_as_attachment(card: dict[str, Any]) -> dict[str, Any]:
return {
"contentType": "application/vnd.microsoft.card.adaptive",
"content": card,
}
def create_hero_card(
title: str,
text: str,
images: list[str] | None = None,
buttons: list[dict[str, str]] | None = None,
) -> dict[str, Any]:
card: dict[str, Any] = {
"contentType": "application/vnd.microsoft.card.hero",
"content": {"title": title, "text": text},
}
if images:
card["content"]["images"] = [{"url": url} for url in images]
if buttons:
card["content"]["buttons"] = [
{"type": "imBack", "title": btn["title"], "value": btn["value"]} for btn in buttons
]
return card