已解决:使用 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 4 和 Node.js 作为 MS 开发一个机器人-Teams 应用程序。该机器人已在 Azure 中正确注册,但出于测试目的使用 Ngrok 转发在本地托管。
机器人的目标是向 api 发出外部请求,然后根据 api 的响应填充自适应卡片模板,以获得比文本更具吸引力的界面。
此模板是使用 Adaptive Card Designer 创建的,在其中呈现得很好。使用模拟器测试机器人也可以。与网络聊天频道相同,查看屏幕截图。
但是,在团队中安装时,不会显示卡片并引发错误。
我在 Teams 中聊天时遇到的错误:
- 我收到的不是卡片,而是一条消息“机器人遇到错误或错误。要继续 运行 这个机器人,请修复机器人源代码。”这是由 adapter.onTurnError 抛出的,它集成在所有 Microsoft 示例中,此 bot 也是基于它。
- 在机器人 运行 正在运行的控制台中,它显示:“[onTurnError] 未处理的错误:错误:未知”。
- 在 Azure 的频道概览中,出现时间戳和消息: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 不能,我不知道而且可能永远不会
我正在使用 Bot Framework SDK 4 和 Node.js 作为 MS 开发一个机器人-Teams 应用程序。该机器人已在 Azure 中正确注册,但出于测试目的使用 Ngrok 转发在本地托管。
机器人的目标是向 api 发出外部请求,然后根据 api 的响应填充自适应卡片模板,以获得比文本更具吸引力的界面。
此模板是使用 Adaptive Card Designer 创建的,在其中呈现得很好。使用模拟器测试机器人也可以。与网络聊天频道相同,查看屏幕截图。 但是,在团队中安装时,不会显示卡片并引发错误。
我在 Teams 中聊天时遇到的错误:
- 我收到的不是卡片,而是一条消息“机器人遇到错误或错误。要继续 运行 这个机器人,请修复机器人源代码。”这是由 adapter.onTurnError 抛出的,它集成在所有 Microsoft 示例中,此 bot 也是基于它。
- 在机器人 运行 正在运行的控制台中,它显示:“[onTurnError] 未处理的错误:错误:未知”。
- 在 Azure 的频道概览中,出现时间戳和消息: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 不能,我不知道而且可能永远不会