已解决:使用 Bot Framework SDK 4 将自适应卡发送到 MS Teams 时出现 onTurnError

SOLVED: Getting onTurnError when sending adaptive card to MS Teams using Bot Framework SDK 4

我正在使用 Bot Framework SDK 4Node.js 作为 MS 开发一个机器人-Teams 应用程序。该机器人已在 Azure 中正确注册,但出于测试目的使用 Ngrok 转发在本地托管。

机器人的目标是向 api 发出外部请求,然后根据 api 的响应填充自适应卡片模板,以获得比文本更具吸引力的界面。

此模板是使用 Adaptive Card Designer 创建的,在其中呈现得很好。使用模拟器测试机器人也可以。与网络聊天频道相同,查看屏幕截图。 但是,在团队中安装时,不会显示卡片并引发错误。

我在 Teams 中聊天时遇到的错误:

所有其他对话框和消息都正常工作,当我将自适应卡作为附件发送到 activity 时,就会发生此错误。

接下来我将粘贴一些代码片段,为我尝试做的事情提供一些背景信息

我创建的模板:

{
    "type": "AdaptiveCard",
    "$schema": "https://adaptivecards.io/schemas/1.2.0/adaptive-card.json",
    "version": "1.2",
    "contentType": "application/vnd.microsoft.card.adaptive",
    "speak": "not set",
    "body": [
        {
            "type": "ColumnSet",
            "columns": [
                {
                    "type": "Column",
                    "width": "auto",
                    "items": [
                        {
                            "type": "TextBlock",
                            "size": "Large",
                            "text": "${title}",
                            "weight": "Bolder"
                        }
                    ]
                },
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "TextBlock",
                            "text": "${type}",
                            "color": "Accent",
                            "height": "stretch",
                            "fontType": "Default",
                            "size": "Large",
                            "weight": "Lighter"
                        }
                    ]
                },
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "TextBlock",
                            "text": "Private",
                            "horizontalAlignment": "Right",
                            "height": "stretch",
                            "size": "Large",
                            "color": "Warning",
                            "$when": "${$root.private == '1'}"
                        }
                    ]
                }
            ]
        },
        {
            "type": "TextBlock",
            "spacing": "None",
            "text": "Created: {{DATE(${createdUtc},SHORT)}}",
            "isSubtle": true,
            "size": "Small",
            "weight": "Lighter",
            "wrap": true,
            "$when": "${$root.createdSet == '1'}"
            
        },
        {
            "type": "ColumnSet",
            "columns": [
                {
                    "type": "Column",
                    "width": "auto",
                    "items": [
                        {
                            "type": "TextBlock",
                            "text": "Creator",
                            "weight": "Bolder",
                            "color": "Accent"
                        },
                        {
                            "type": "TextBlock",
                            "weight": "Bolder",
                            "text": "${creator.name}",
                            "wrap": true,
                            "spacing": "None"
                        }
                    ],
                    "spacing": "Medium"
                },
                {
                    "type": "Column",
                    "width": "auto",
                    "items": [
                        {
                            "type": "TextBlock",
                            "text": "Assignee",
                            "weight": "Bolder",
                            "color": "Accent"
                        },
                        {
                            "type": "TextBlock",
                            "weight": "Bolder",
                            "wrap": true,
                            "spacing": "None",
                            "text": "${$root.assignee.name}"
                        }
                    ],
                    "spacing": "Medium"
                },
                {
                    "type": "Column",
                    "width": "auto",
                    "spacing": "Medium",
                    "items": [
                        {
                            "type": "TextBlock",
                            "text": "Priority",
                            "weight": "Bolder",
                            "color": "Accent"
                        },
                        {
                            "type": "TextBlock",
                            "weight": "Bolder",
                            "text": "${priority}",
                            "wrap": true,
                            "spacing": "None"
                        }
                    ]
                },
                {
                    "type": "Column",
                    "width": "auto",
                    "spacing": "Medium",
                    "items": [
                        {
                            "type": "TextBlock",
                            "weight": "Bolder",
                            "color": "Accent",
                            "text": "Status"
                        },
                        {
                            "type": "TextBlock",
                            "weight": "Bolder",
                            "text": "${status}",
                            "wrap": true,
                            "spacing": "None"
                        }
                    ]
                },
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "TextBlock",
                            "text": "Due Date",
                            "weight": "Bolder",
                            "color": "Accent"
                        },
                        {
                            "type": "TextBlock",
                            "weight": "Bolder",
                            "text": "{{DATE(${dueUTC},SHORT)}}",
                            "wrap": true,
                            "spacing": "None"
                        }
                    ],
                    "spacing": "Medium",
                    "$when": "${$root.status!= 'Done' && ${$root.dueSet == '1'}}"
                },
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "TextBlock",
                            "text": "Done Date",
                            "weight": "Bolder",
                            "color": "Accent"
                        },
                        {
                            "type": "TextBlock",
                            "weight": "Bolder",
                            "text": "{{DATE(${doneUTC},SHORT)}}",
                            "wrap": true,
                            "spacing": "None"
                        }
                    ],
                    "spacing": "Medium",
                    "$when": "${$root.status== 'Done' && ${$root.doneSet == '1'}}"
                }
            ],
            "spacing": "Small",
            "separator": true
        },
        {
            "type": "TextBlock",
            "text": "${description}",
            "wrap": true,
            "separator": true
        }
    ],
    "actions": [
        {
            "type": "Action.OpenUrl",
            "title": "View",
            "url": "${viewUrl}"
        }
    ]
    
}

我在 issueCardGenerator class:

中填充了这样的模板变量

(“问题”是一个包含我想作为自适应卡片发送给用户的信息的对象。 “issueCard”是对 json 模板的引用。我如何解析信息的细节在这里并不重要。 “翻译”例如将问题对象的一些枚举翻译成自然语言。)

generateIssueCard(issue){
        //render card
        var template = new Template(issueCard);
            
        var cardPayload = template.expand({
            $root: {
                title: issue.title==null ? "No Title available" : issue.title,
                description: issue.description==null ? "": issue.description, 
                creator: {
                    name: issue.creator==null ? "Not Set" : issue.creator.username 
                },
                assignee: {
                    name: issue.assignee==null ? "Not Set" : issue.assignee.username 
                },
                type: issue.type==null ? "" : this.translate(issue.type.value),
                status: issue.status==null ? "No Status" : this.translate(issue.status.value),
                priority: issue.priority==null ? "No Priority" : this.translate(issue.priority.value),

                //only display times when they are set
                dueSet: issue.due_date==null ? "0" : "1",
                doneSet: issue.done_date==null ? "0" : "1",
                createdSet: issue.created==null ? "0" : "1",

                //convert time format
                dueUtc: issue.due_date==null ? "No Due Date" : moment(issue.due_date).format('YYYY-MM-DD')+'T06:08:39Z',
                createdUtc: issue.created==null ? "No Creation Time" : moment(issue.created).format('YYYY-MM-DD')+'T06:08:39Z',
                doneUtc: issue.done_date==null ? "No Done Date" : moment(issue.done_date).format('YYYY-MM-DD')+'T06:08:39Z',

                viewUrl: "https://www.some-url.com",
                private: issue.private==null ? "0" :issue.private
            }
         });

        var adaptiveCard = new AdaptiveCard();
        adaptiveCard.parse(cardPayload);
        return(CardFactory.adaptiveCard(adaptiveCard))

    }

然后在我的对话步骤中像这样发送它,它是瀑布对话的一部分:

(用户输入一个“idInput”来指定问题的id,然后请求并保存在“issue”中)

async summaryStep(stepContext) {

    const idInput = stepContext.result;
    if (idInput) {
        var issue;

        //do request and set issue variable here. This works fine and shows proper in the logs
        issue= await this.doRequest(url,accessToken,"GET");
        
        var cardGenerator=new IssueCardGenerator();

        var activity= {
            text: 'This is your requested issue with the id ' +idInput +': ',
            attachments: [cardGenerator.generateIssueCard(issue)]
        }

        await stepContext.context.sendActivity(activity);

    }
    return await stepContext.endDialog();

解决了“问题”

如果有人遇到同样的问题,这里是解决方案:

原来在我的 CardGenerator 中...

var adaptiveCard = new AdaptiveCard();
adaptiveCard.parse(cardPayload);
return(CardFactory.adaptiveCard(adaptiveCard))

..需要更改为直接为 cardfactory 使用负载:

return(CardFactory.adaptiveCard(cardPayload))

为什么其他渠道使用第一个变体可以正确处理此问题而 Teams 不能,我不知道而且可能永远不会