I am trying to send a Teams notification with a hero card or an Adaptive Card. I can send a simple text message as a notification.
I don't know how to wrap a Hero card or an Adaptive Card in an Activity as SendToConversationAsync only accepts an Activity.
Here is the code which you can use to send any card created using the adaptivecard.io
const resultOutputCard = {
"type": "AdaptiveCard",
"version": "1.0",
"body": [
{
"type" : "TextBlock",
"text" : "Sample Text"
}
],
"actions": [
{
"type": "Action.OpenUrl",
"title": "Google Link",
"url": "www.google.com"
}
],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json"
};
const card = CardFactory.adaptiveCard(resultOutputCard);
await step.context.sendActivity({ attachments: [card] });
Below is the code which you can use to send hero card
const { MessageFactory, CardFactory } = require('botbuilder');
const card = CardFactory.heroCard(
'White T-Shirt',
['https://example.com/whiteShirt.jpg'],
['buy']
);
const message = MessageFactory.attachment(card);
await context.sendActivity(message);
Below is the link where you can find the above example.
https://learn.microsoft.com/en-us/javascript/api/botbuilder-core/cardfactory?view=botbuilder-ts-latest
Related
We are trying to add email regex validation in Adaptive cards in MSTeams.The following card is working expexted in Adaptive Card Schema Designer. It validates the input and for invalid email id format it shwos the error.
let card = {
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.3",
"body": [
{
"type": "TextBlock",
"text": "Specify the type of text being requested:"
},
{
"type": "Input.Text",
"id": "myComment",
"label": "style: text",
"errorMessage": "Please provide valid email-id",
"regex": "^[A-Za-z0-9._%+-]+#[A-Za-z0-9.-]+[.][A-Za-z0-9-]{2,4}$"
}
],
"actions": [
{
"type": "Action.Submit",
"title": "OK"
}
]
}
When we post the same card to a MSteams channel using sendmessage API it renders the card correctly. But valid email-id also it throws the error message.
The following request body is used :
let contentData = JSON.stringify(card);
{
body: {
contentType: 'html',
content: `<attachment id=${cardAttachmentUUID}>`
},
attachments: [{
id: cardAttachmentUUID,
contentType: 'application/vnd.microsoft.card.adaptive',
content: contentData
}]
}
MSTeams API requires card body to in String. Hence we stringify the card using JSON.stringify(card)
Is that because of the JSON.Stringify() ? Without JSON.Stringify() MSTeams API throws invalid input format error .
Please help us to resolve this issue?
I do have a Bot that is reachable via MS Teams. The bot sends an Adaptive Card with some Text and a submit-action. When the user clicks on this submit-action, I want to proceed the input and then update the prior sent Adaptive card via calling context.updateActivity. According to documentation, I can use activity.Id = turnContext.Activity.ReplyToId; to specify the message I want to update. But the call of context.updateActivity results in a 400 HTTP error, the message is "Unknown activity type".
Some investigation:
This error occurs when I want to send another Adaptive Card and when I want to send plain text
I verified, that the id of sendActivity is the same as turnContext.Activity.ReplyToId
Any idea?
Here is my code:
Adaptive Card
{
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.0",
"body": [
{
"type": "TextBlock",
"text": "Some text",
"wrap": true
},
{
"type": "Input.ChoiceSet",
"id": "Feedback",
"value": "",
"style": "compact",
"placeholder": "Wie hilfreich war diese Antwort?",
"choices": [
{
"title": "⭐",
"value": "1"
},
{
"title": "⭐⭐",
"value": "2"
},
{
"title": "⭐⭐⭐",
"value": "3"
},
{
"title": "⭐⭐⭐⭐",
"value": "4"
},
{
"title": "⭐⭐⭐⭐⭐",
"value": "5"
}
]
}
],
"actions": [
{
"title": "Feedback absenden",
"type": "Action.Submit"
}
]
}
Sending the message:
private handleMessage = async (context: TurnContext, next: () => Promise<void>): Promise<void> => {
const adaptiveCard = AdaptiveCardFactory.createAdaptiveCardFromTemplateAndData(AnswerWithFeedbackCard);
const result = await context.sendActivity({ attachments: [adaptiveCard] });
console.error("send msg with id " + result?.id);
}
code to update the message:
private handleMessage = async (context: TurnContext, next: () => Promise<void>): Promise<void> => {
console.error("received msg with id " + context.activity.replyToId);
if (context.activity.value && !context.activity.text) {
const updatedCard = CardFactory.adaptiveCard(this.botConfig.updatedCard);
await context.updateActivity({ text: "updated :)", id: context.activity.replyToId});
//or
await context.updateActivity({ attachments: [updatedCard], id: context.activity.replyToId});
}
}
Got it!
const att = AdaptiveCardFactory.createAdaptiveCardFromTemplateAndData(AnswerWithAnsweredFeedbackCard, {
answer: "Mir geht's super, danke der Nachfrage!",
starsCount: context.activity.value.Feedback
});
const id = await context.updateActivity( { attachments: [ att ], id: context.activity.replyToId} );
This does not work, but this does:
const att = AdaptiveCardFactory.createAdaptiveCardFromTemplateAndData(AnswerWithAnsweredFeedbackCard, {
answer: "Mir geht's super, danke der Nachfrage!",
starsCount: context.activity.value.Feedback
});
const msg = MessageFactory.attachment( att )
msg.id = context.activity.replyToId;
const id = await context.updateActivity( msg )
So, you need to save the save the sent msg in a variable and set the id of this variable instead of using the "inplace"-implementation of updateActivity
I am using Adaptive card to display some items in my bot solution.
In Adaptive card Submit button i want to make title as bold.
Code:
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"body": [
{
"maxLines": 0,
"size": "default",
"spacing": "medium",
"text": "You can ask me below optons",
"type": "TextBlock",
"weight": "default",
"wrap": true
}
],
"actions": [
{
"type": "Action.Submit",
"title": "Service details \n \"Service details for PC request\"",
"data": "Service details for PC request"
}
],
"type": "AdaptiveCard",
"version": "1.0"
}
In the above code.I am showing title in submit button two lines.
In this i want to make only "Service details" in bold.
Is there any option for submit action styling?
I have tried Bold(** {Something} **) option. But didnt work for Button title.
Unfortunately, it appears that rendering Markdown is not supported by Adaptive Cards for the action component. As you can see in the AC docs, Markdown is only supported in the TextBlock. Scrolling down to Actions, you can see that it is not.
If this is a feature you feel strongly about, I would suggest you create a feature request on their GitHub repo.
[Edit]
It is possible to change the text of the button after the card has been passed to Web Chat but prior to its rendering. Add the following code, making adjustments where necessary, and you should be good to go.
mainDialog.js - Pass placeholder text in the adaptive card being sent from the bot.
async basicAdaptiveCard ( stepContext ) {
let text = `##Service details` // \n \"Service details for PC request\""
let response = md.utils.isString( '__Service details__' )
const card = {
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.0",
"body": [
{
"type": "TextBlock",
"text": "Hi!! How can I help you today?",
"weight": "Bolder",
"size": "Medium"
}
],
"actions": [
{
"type": "Action.Submit",
"title": "Placeholder Message", // This text will be replaced in Web Chat
"data": "close"
}
]
}
index.html
<head>
[...]
<script type="text/javascript" src="https://unpkg.com/markdown-it#8.4.2/dist/markdown-it.min.js"></script>
[...]
</head>
[...]
<script type="text/babel">
( async function () {
'use strict';
const { ReactWebChat } = window.WebChat;
const markdownIt = window.markdownit(); // Import 'markdown-it' into web chat script
[...]
// Create `store` to capture and modify the activity coming from the bot
const store = window.WebChat.createStore( {}, ( { dispatch } ) => next => async action => {
// Notifies Web Chat we are going to do something when an activity is received from the bot
if ( action.type === 'DIRECT_LINE/INCOMING_ACTIVITY' ) {
// We update the HTML of the already rendered card.
// First, we acquire the button(s). In my case, I have multiple buttons from multiple cards, so I gather them all.
let button = document.body.getElementsByClassName('ac-pushButton')
// Next, we cycle through the buttons
for(let i = 0; i <= button.length - 1; i++) {
// Looking for the button with the text we passed through
if(button[i].children[0].nodeName === 'DIV' && button[i].children[0].innerHTML === 'Placeholder Message') {
// As the default font-weight for the button is bold, we set it all to 'normal'
button[i].children[0].setAttribute('style', 'font-weight: normal; color: black')
// And pass in the text we want with the styling we want allowing us to specify which text should be bold
button[i].children[0].innerHTML = '<p><b>Service details</b><br />\"Service details for PC request\"</p> '
continue;
}
}
return next( action );
} );
// Finally, we pass in `store` to the renderer
window.ReactDOM.render(
<ReactWebChat
directLine={ directLine }
store={store}
/>,
document.getElementById( 'webchat' )
);
document.querySelector( '#webchat > *' ).focus();
Hope of help.
I am having an adaptive card which will be displayed in waterfall dialog. I want to know is it possible to capture the tap action on the adaptive card I don't want to add button to handle click. I am using v4 version of bot framework.
Tap dialog is triggered twice
My adaptive card :
var Card1 = {
"type": "AdaptiveCard",
"selectAction": {
"type": "Action.Submit",
"id": "tap",
"title": "tap",
"data": "data": { tap: 'tap' }
},
"backgroundImage": "https://download-ssl.msgamestudios.com/content/mgs/ce/production/SolitaireWin10/dev/adapative_card_assets/v1/card_background.png",
"body": [
{
"type": "ColumnSet",
"columns": [
{
"type": "Column",
"items": [
{
"type": "Image",
"url": "https://download-ssl.msgamestudios.com/content/mgs/ce/production/SolitaireWin10/dev/adapative_card_assets/v1/tile_spider.png",
"size": "Stretch"
}
],
"width": 1
},
{
"type": "Column",
"items": [
{
"type": "TextBlock",
"horizontalAlignment": "Center",
"weight": "Bolder",
"color": "Light",
"text": "Click here to play another game of Spider in Microsoft Solitaire Collection!",
"wrap": true
}
],
"width": 1
}
]
}
],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "0.5"
};
This is how i have updated the code
Code
// Add prompts that will be used by the main dialogs.
this.dialogs.add(new TextPrompt(TAP_PROMPT, () => true));
// Create a dialog that asks the user for their name.
this.dialogs.add(new WaterfallDialog(PUBLICTRANS, [
this.promptForTap.bind(this),
this.captureTap.bind(this)
]));
async promptForTap(step) {
var Card1 = {
"type": "AdaptiveCard",
"selectAction": {
"type": "Action.Submit",
"id": "tap",
"title": "tap",
"data": "data": { tap: 'tap' }
},
"backgroundImage": "https://download-ssl.msgamestudios.com/content/mgs/ce/production/SolitaireWin10/dev/adapative_card_assets/v1/card_background.png",
"body": [
{
"type": "ColumnSet",
"columns": [
{
"type": "Column",
"items": [
{
"type": "Image",
"url": "https://download-ssl.msgamestudios.com/content/mgs/ce/production/SolitaireWin10/dev/adapative_card_assets/v1/tile_spider.png",
"size": "Stretch"
}
],
"width": 1
},
{
"type": "Column",
"items": [
{
"type": "TextBlock",
"horizontalAlignment": "Center",
"weight": "Bolder",
"color": "Light",
"text": "Click here to play another game of Spider in Microsoft Solitaire Collection!",
"wrap": true
}
],
"width": 1
}
]
}
],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.0"
};
const reply = {
attachments: [CardFactory.adaptiveCard(Card1)]
};
return await step.prompt(TAP_PROMPT, { prompt: reply });
}
async captureTap(step) {
// Edited from original answer
await step.context.sendActivity(`You selected `);
// Send Back Channel Event
await step.context.sendActivity({ type: 'event', name: 'tapEvent' });
return await step.endDialog();
}
output the card is triggered twice
You can send a Back Channel Event to WebChat from the Bot when the user clicks on an AdaptiveCard to trigger an event on the web page.
Simple AdaptiveCard with Select Action
First, create an AdaptiveCard that has a selectAction. Note, you can either add a selectAction to the whole card or different components in the card like an image or a column. When the user clicks on a part of the card that has a selectAction, it will send an activity to the Bot that contains the data attribute from the action that we can use to determine which action was triggered on the bot side.
Be sure to set the title attribute, the data field, and the type which should be Action.Submit.
{
"type": "AdaptiveCard",
"selectAction": {
"type": "Action.Submit",
"id": "tap",
"title": "tap",
"data": { "tap": "Play again"}
},
"body": [
{
"type": "TextBlock",
"horizontalAlignment": "Center",
"size": "Medium",
"weight": "Bolder",
"text": "Click Me!"
},
{
"type": "Image",
"url": "https://usercontent2.hubstatic.com/13896379.jpg"
}],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.0"
}
Bot Code - Node Js
Constructor
const TAP_PROMPT = 'tap_prompt';
constructor(conversationState, userState) {
...
// Add prompts that will be used by the main dialogs.
this.dialogs.add(new TextPrompt(TAP_PROMPT, () => true));
// Create a dialog that asks the user for their name.
this.dialogs.add(new WaterfallDialog(WHO_ARE_YOU, [
this.promptForTap.bind(this),
this.captureTap.bind(this)
]));
}
AdaptiveCard Prompt
In one of the steps of your Waterfall, you can create a reply object that has the AdaptiveCard as an attachment. Then you can pass that reply to the prompt. I would recommend using a TextPrompt for this step.
// This step in the dialog sends the user an adaptive card with a selectAction
async promptForTap(step) {
const reply = {
attachments: [CardFactory.adaptiveCard(Card)]
}
return await step.prompt(TAP_PROMPT, { prompt: reply });
}
Capture AdaptiveCard selecteAction
In the next step of the Waterfall, you can get the data attribute sent from the AdaptiveCard by accessing step.value. Here, send an activity to the bot with a type and a name property. These two attributes will be used to filter incoming activities in WebChat and trigger the proper event.
// This step captures the tap and sends a back channel event to WebChat
async captureTap(step) {
// Edited from original answer
await step.context.sendActivity(`You selected ${step.context.activity.value.tap}`);
// Send Back Channel Event
await step.context.sendActivity({type: 'event', name: 'tapEvent'});
return await step.endDialog();
}
WebChat Middleware
In WebChat, we are going to create a custom middleware to check incoming activities. When we encounter an activity that has a name and type that we recognize, trigger your event on the webpage. In the example below, I just alerted the use that they clicked on the AdaptiveCard.
const store = window.WebChat.createStore(
{},
({ dispatch }) => next => action => {
if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY') {
const { name, type } = action.payload.activity;
if (type === 'event' && name === 'tapEvent') {
alert("You tapped on the AdaptiveCard.");
}
}
return next(action);
}
);
window.WebChat.renderWebChat({
directLine: window.WebChat.createDirectLine({ token }),
store,
}, document.getElementById('webchat'));
For more details on back channel events and creating a custom middleware in WebChat, checkout this sample in the WebChat Repo.
Hope this helps!
I understand that adaptive cards are down-rendered as image on channels that does not support them. I just want to know how to either remove or set the "Title" element as shown on the fb channel:
The AdaptiveCard.Title element is deprecated and I did try setting that, it did not have any effect.
Here is my sample json:
{
"type": "AdaptiveCard",
"body": [
{
"type": "TextBlock",
"id": "Title",
"horizontalAlignment": "Center",
"size": "Large",
"weight": "Bolder",
"text": "See results on our website!"
},
{
"type": "Image",
"horizontalAlignment": "Center",
"url": "mylogo.png",
"size": "Stretch"
},
{
"type": "TextBlock",
"id": "Subtitle",
"horizontalAlignment": "Center",
"size": "ExtraLarge",
"text": "This channel does not allow us to display your results. Click the button to view it on our website.",
"wrap": true
}
],
"actions": [
{
"type": "Action.OpenUrl",
"id": "OpenUrl",
"title": "Take me there!"
}
],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.0"
}
Unfortunately, when the BotFramewrok renders the card into an image for Facebook Messenger it adds the title above the actions which is strange. The only way to get rid of it is to remove the action from the card, which defeats its purpose in this case. An alternative is to send a Facebook Button Template in the activity's channel data instead of the adaptive card when the user is on Facebook Messenger. For more details checkout the Facebook Documentation on Button Templates and the code snippet below.
Screenshot
Bot Code - Node
async onTurn(turnContext) {
if (turnContext.activity.type === ActivityTypes.Message) {
if (turnContext.activity.channelId === 'facebook') {
await turnContext.sendActivity({
channelData: {
"attachment": {
"type": "template",
"payload": {
"template_type":"button",
"text":"This channel does not allow us to display your results. Click the button to view it on our website.",
"buttons":[{
"type":"web_url",
"url":"https://www.microsoft.com",
"title":"Take me there!"
}]
}
}
}
});
} else {
await turnContext.sendActivity({
attachments: [this.createAdaptiveCard()],
});
}
}
}
Hope this helps!
I had the same problem but I find an alternative using HeroCard.
If you are developing it with C# there it goes the code:
// first of all check if it is Facebook channel
// note that Channels is from Microsoft.Bot.Connector, the old one is deprecated.
if (turnContext.Activity.ChannelId == Channels.Facebook)
{
Activity replyToConversation = _flowService.ConvertMarkdownUrlToFacebookUrl(turnContext.Activity, response.Answer);
await turnContext.SendActivityAsync(replyToConversation, cancellationToken: cancellationToken);
}
public Activity ConvertMarkdownUrlToFacebookUrl(Activity message, string queryResponse)
{
var buttons = getButtons(queryResponse, out string result);
Activity replyToConversation = message.CreateReply();
replyToConversation.Attachments = new List<Attachment>();
List<CardAction> actions = new List<CardAction>();
foreach (var button in buttons)
{
actions.Add(new CardAction()
{
Title = button.Key, // text hyperlink
Type = ActionTypes.OpenUrl,
Value = button.Value // url
});
}
Attachment attachment = new HeroCard
{
Text = result, // your text
Buttons = actions
}.ToAttachment();
replyToConversation.Attachments.Add(attachment);
return replyToConversation;
}
You'll obtain something like this:
(sorry I had to delete the text)
Maybe it's not perfect (my first time with cards and attachments) but I hope that this will help someone 🙂