UpdateActivity throws 400 "Unknown activity type" - botframework

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

Related

Triggering an Action.Execute on an adaptive card sent by a chat bot on Teams mobile apps caused a "Something Went Wrong" error

Versions
Package: botbuilder#4.11.0
Nodejs: v16.19.0
Teams Android Client: 1416/1.0.0/2023012702/0115
I have a chatbot that functions as a notification bot that sends an adaptive card notification to our users.
On the card, the user can reply to the notification by typing in the Input.Text field and then clicking a send button that triggers an Action.Execute, our Nodejs bot will then process the activity by sending notifications to corresponding user and then updating the card with a new card.
This works perfectly on Web and Desktop app but in the Teams Android App the card will briefly shows an error message saying "Something went wrong" before our card is finally updated.
These is the card that I send:
{
"type": "AdaptiveCard",
"appId": process.env.MicrosoftBotAppId,
"body": [
{
"type": "TextBlock",
"text": message,
"wrap": true
},
{
"id": "history_title",
"type": "TextBlock",
"text": cardString.pastEvents,
"wrap": true
},
{
"type": "Container",
"spacing": "None",
"items": [
{
"id": "last_trail_name",
"type": "TextBlock",
"text": lastTrail ? cardString.action(lastTrail.userName, lastTrailAction) : "",
"spacing": "None",
// "size": "Small",
"wrap": true
},
{
"id": "last_trail_comment",
"type": "TextBlock",
"text": lastTrail ? `${lastTrail.comment.replace(/<\/?[^>]+(>|$)/g, "")}` : "",
"spacing": "None",
// "size": "Small",
"wrap": true
},
{
"id": "last_trail_date",
"type": "TextBlock",
"text": lastTrail ? `${lastTrailDateString}` : "",
"spacing": "None",
"isSubtle": true,
"size": "Small",
"wrap": true
}
]
},
{
"type": "Container",
"items": [
{
"id": "second_last_trail_name",
"type": "TextBlock",
"text": secondLastTrail ? cardString.action(secondLastTrail.userName, secondLastTrailAction) : "",
"spacing": "None",
// "size": "Small",
"wrap": true
},
{
"id": "second_last_trail_comment",
"type": "TextBlock",
"text": secondLastTrail ? `${secondLastTrail.comment.replace(/<\/?[^>]+(>|$)/g, "")}` : "",
"spacing": "None",
// "size": "Small",
"wrap": true
},
{
"id": "second_last_trail_date",
"type": "TextBlock",
"text": secondLastTrail ? `${secondLastTrailDateString}` : "",
"spacing": "None",
"isSubtle": true,
"size": "Small",
"wrap": true
}
]
},
{
"id": "comment",
"isRequired": true,
"type": "Input.Text",
"placeholder": cardString.commentPlaceholder,
"isMultiline": true
},
{
"id": "success-msg",
"type": "TextBlock",
"text": success ? cardString.commentSentAlert : "",
"spacing": "None",
"isSubtle": true,
"size": "Small",
"wrap": true,
"color": "good"
}
],
"actions": [
{
// "tooltip": isPremium ? cardString.send : cardString.premiumTooltip,
// "isEnabled": isPremium,
"type": "Action.Execute",
"verb": "comment",
"title": cardString.send,
"data": data,
},
{
"type": "Action.OpenUrl",
"title": cardString.goToTicketButton,
"url": deeplink
}
],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.4"
}
And this is the bot that processed the action:
class Bot extends TeamsActivityHandler {
constructor() {
super();
this.onMembersAdded(async (turnContext: TurnContext, next: () => Promise<any>) => {
const gettingStartedUrl: string = 'https://www.teamswork.app/gettingstarted';
const membersAdded = turnContext.activity.membersAdded;
for (let cnt = 0; cnt < membersAdded.length; cnt++) {
if (membersAdded[cnt].id !== turnContext.activity.recipient.id) {
const welcomeMessage = "Welcome message"
// add conv references
if (await this.storeConversationReference(turnContext))
await turnContext.sendActivity(welcomeMessage); // Only send notification if conversation reference store (one time)
}
}
// By calling next() you ensure that the next BotHandler is run.
await next();
});
}
// Listener for activity e.g. comment activity on notification card
async onInvokeActivity(turnContext: TurnContext): Promise<InvokeResponse<any>> {
// Listen for activity invocation and check if the comment action on adaptiveCard has been called
const activity: Activity = turnContext.activity;
// context.log('activity: ', activity);
// turnContext.sendActivity("Hello World!");
let card: object;
if (activity.name === "adaptiveCard/action") {
// context.log("action activity");
const action = activity.value.action;
// context.log("action : ", action);
if (action.verb === "comment") {
const userName: string = activity.from.name;
const userId: string = activity.from.aadObjectId;
const data: IActionData = action.data;
// context.log('data: ', action.data);
const date: Date = new Date();
const tenantId: string = activity.conversation.tenantId;
const comment: string = data.comment;
const lang: string = data.lang || "en";
// assignee Ticket == user commennt
if ((data.ticket.assigneeId == userId) && (data.ticket.firstTimeResponse == "")) {
this.updateTicket(data.ticket)
}
// Check for ticketId
if (!!!data.ticket) {
context.log("No ticketId found, aborting");
// throw ("No ticketId found, aborting")
}
// Construct new audit trail / comment
let newComment: IAuditTrail = {
userName: userName,
userId: userId,
ticketId: data.ticket.id,
action: "commented",
comment: comment,
date: date,
}
// initialize parameters for card to be sent
const cardContent: ICardContent = {
userName: userName,
type: "comment",
action: "commented",
comment: comment,
isRequestor: false,
}
// Prepare response message
const encodedDeeplink: string = constructDeeplink(data.ticket, data.ticketingAppUrl, data.channelId, data.entityId);
const message: string = constructMessage(data.ticket, data.cardContent, encodedDeeplink, lang);
const isPremium: boolean = await getPremium(tenantId);
// const card = invokeSuccessResponse(encodedDeeplink, message, data);
// Save audit trail
const insertCommentEndpoint: string = process.env["InsertCommentEndpoint"];
try {
await cosmosService.insertItem(insertCommentEndpoint + encodeQueryParams({ instanceId: data.ticket.instanceId }), { comment: newComment });
} catch (error) {
context.log(error);
}
// Send request to SendCardNotification API
try {
// Notification recipient id array
let recipientIds: string[] = [];
if (data.ticket.requestorId)
recipientIds.push(data.ticket.requestorId)
if (data.ticket.assigneeId)
recipientIds.push(data.ticket.assigneeId)
// If ticket have custom fields get people from them
if (data.ticket.customFields) {
// Get instance so we can get customFieldSetting
const instanceContainer: Container = ticketDatabase.container(process.env["InstanceContainer"]);
const { resource: instance } = await instanceContainer.item(data.ticket.instanceId, tenantId).read<IInstance>();
// Create array for user who will receive the notification
let activePersonaCustomFields: ICustomField[] = [];
if (instance.customFieldsLeft)
activePersonaCustomFields.push(...instance.customFieldsLeft);
if (instance.customFieldsRight)
activePersonaCustomFields.push(...instance.customFieldsRight);
activePersonaCustomFields = activePersonaCustomFields.filter((value) => value.type.key === "6_peoplepicker");
activePersonaCustomFields = activePersonaCustomFields.filter((value) => value.isReceiveNotification);
if (activePersonaCustomFields.length > 0) {
for (const fields of activePersonaCustomFields) {
if (data.ticket.customFields[fields.id]?.length > 0) {
const personas: IPersonaProps[] = data.ticket.customFields[fields.id];
for (const element of personas) {
recipientIds.push(element.id)
}
}
}
}
}
// Remove message sender from recipient list
recipientIds = recipientIds.filter((id) => id !== userId);
// Get unique notification recipient array
const uniqueRecipientIds: string[] = [... new Set(recipientIds)];
context.log("Sending notifications");
for (const id of uniqueRecipientIds) {
await this.sendNotification(cardContent, id, tenantId, data);
}
card = NotificationCard(encodedDeeplink, message, data, true, isPremium, lang);
const cardAttachment: Attachment = CardFactory.adaptiveCard(card);
const activityPayload = MessageFactory.attachment(cardAttachment);
activityPayload.id = turnContext.activity.replyToId;
context.log("Updating cards");
await turnContext.updateActivity(activityPayload);
} catch (error) {
context.log(error)
}
}
}
const cardRes = {
statusCode: StatusCodes.OK,
type: 'application/vnd.microsoft.card.adaptive',
value: card
};
const res = {
status: StatusCodes.OK,
body: cardRes
};
return res;
}
}
It actually does not matter what I do on the card, even if I do nothing and then only return the InvokeResponse, the error still happens.
Expected behavior
The card is updated without any error message shown.
Screenshots

How to check if bot/application is uninstalled from ms teams?

I have a bot application which I installed in my team, using team scope. Now when this application is uninstalled from team is there any event I can get/monitor?
I was going over the Microsoft documentation and tried following piece of code. According to this onTeamsMembersRemovedEvent is called when bot or a member is removed.
export class MyBot extends TeamsActivityHandler {
constructor() {
super();
this.onTeamsMembersRemovedEvent(async (membersRemoved: ChannelAccount[], teamInfo: TeamInfo, turnContext: TurnContext, next: () => Promise<void>): Promise<void> => {
let removedMembers: string = '';
console.log(JSON.stringify(membersRemoved));
membersRemoved.forEach((account) => {
removedMembers += account.id + ' ';
});
const name = !teamInfo ? 'not in team' : teamInfo.name;
const card = CardFactory.heroCard('Account Removed', `${removedMembers} removed from ${teamInfo.name}.`);
const message = MessageFactory.attachment(card);
await turnContext.sendActivity(message);
await next();
});
}
}
But for me I do not receive this event when I remove the bot application. I remove the bot application manually from inside the apps by choosing uninstall.
When uninstalled the app by navigating to More options > Manage teams > Apps > Uninstall , teamMemberRemoved event got fired and got correct response.
{
"membersRemoved": [
{
"id": "28:aXXXXX04-e293-447c-951f-6a6971b3b66b"
}
],
"type": "conversationUpdate",
"timestamp": "2021-10-19T15:24:45.9499395Z",
"id": "f:e0d2c276-XXXXX-5d74-73ad-3c67b9b0ae4f",
"channelId": "msteams",
"serviceUrl": "https://smba.trafficmanager.net/amer/",
"from": {
"id": "29:1qanOqiaR5gWE-aWoYPdYjB--mUmmVQFGddHxyb37WXc4FI-eD62pSxBJYoXXXXXeGyFlpiTzRd-fTCiBmNbeuQg",
"aadObjectId": "XXXXXc4d0-XXXXX-4154-a85f-a89cd77aefa8"
},
"conversation": {
"isGroup": true,
"conversationType": "channel",
"tenantId": "3XXXXXef-XXXXX-4d60-XXXXX-0aXXXXX693df",
"id": "19:XXXXX53a099498f9e08679e58f1f7fc#thread.tacv2"
},
"recipient": {
"id": "28:aXXXXX-e293-XXXXX-951f-6a6971b3b66b",
"name": "XXXXX"
},
"channelData": {
"team": {
"aadGroupId": "XXXXXf3-fa01-XXXXX-bb62-201225dce9e4",
"name": "XXXXX",
"id": "19:XXXXX099498f9e08679eXXXXXf7fc#thread.tacv2"
},
"eventType": "teamMemberRemoved",
"tenant": {
"id": "36a708ef-XXXX-4d60-9de0-XXXXXXdf"
}
}}

How do I tell Alexa to prompt user for input

I need to tell alexa to prompt user for input then store that input in a variable to be used in my code.
InvocationName: send mail
Alexa: Tell me mail subject
User: Test email
Alexa: Okay, tell me message body.
User: This is just a sample test
Alexa, okay, tell me receiver email
User: test#gmail.com
Below is my intent schema:
{
"interactionModel": {
"languageModel": {
"invocationName": "send mail",
"intents": [
{
"name": "AMAZON.CancelIntent",
"samples": []
},
{
"name": "AMAZON.HelpIntent",
"samples": []
},
{
"name": "AMAZON.StopIntent",
"samples": []
},
{
"name": "AMAZON.FallbackIntent",
"samples": []
},
{
"name": "AMAZON.NavigateHomeIntent",
"samples": []
},
{
"name": "SendMailIntent",
"slots": [
{
"name": "ReceiverEmail",
"type": "AMAZON.SearchQuery"
}
],
"samples": [
"mail",
"send mail"
]
}
],
"types": []
},
"dialog": {
"intents": [
{
"name": "SendMailIntent",
"confirmationRequired": false,
"prompts": {},
"slots": [
{
"name": "ReceiverEmail",
"type": "AMAZON.SearchQuery",
"confirmationRequired": false,
"elicitationRequired": true,
"prompts": {
"elicitation": "Elicit.Slot.838288524310.965699312002"
}
}
]
}
],
"delegationStrategy": "ALWAYS"
},
"prompts": [
{
"id": "Elicit.Slot.838288524310.965699312002",
"variations": [
{
"type": "PlainText",
"value": "Enter subject"
}
]
}
]
}
}
and below is the code I have been able to come up with:
// sets up dependencies
const Alexa = require('ask-sdk-core');
const i18n = require('i18next');
const languageStrings = require('./languageStrings');
const SendMailHandler = {
canHandle(handlerInput) {
const request = handlerInput.requestEnvelope.request;
// var code = this.event.request.intent.slots.code.value;
//console.log(code)
// checks request type
return request.type === 'LaunchRequest'
|| (request.type === 'IntentRequest'
&& request.intent.name === 'SendMailIntent');
},
handle(handlerInput) {
const speechText = 'Ok. Tell me the mail subject'
const response = handlerInput.responseBuilder
.speak(speechText)
.reprompt(speechText) // <--- Here is our reprompt
.getResponse();
console.log(response)
return response;
},
};
// Omitted default Alexa handlers
const skillBuilder = Alexa.SkillBuilders.custom();
exports.handler = skillBuilder
.addRequestHandlers(
SendMailHandler,
)
.lambda();
You should use dialog management with 2 slots.
As I can see currently you only collect one slot (ReceiverEmail) with dialoge management.
But you need also to create a slot for the text you want to send.
Later in your code you need to check if the dialogue is in status COMPLETED.
See the example https://github.com/alexa/skill-sample-nodejs-petmatch/ or this video: https://www.youtube.com/watch?v=u99WMljnQXI

Alexa skill doesn't execute intent

I have a skill, I'm trying to test it with the "test" function in alexa developer console. If I give the invocation name, it gets recognized to be the specific intent, but the response doesn't match. (It might be something glaringly obvious that I just can't notice anymore.)
I have a LaunchRequest type, it works with the invocation name.
const LaunchRequestHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
},
handle(handlerInput) {
welcomeMessage = `updated welcome`;
return handlerInput.responseBuilder
.speak(welcomeMessage)
.reprompt(helpMessage)
.getResponse();
},
};
(welcomeMessage is declared outside, this was just testing if the issue was giving it new value)
However, when it comes to an intent based on user input (in this case TestIntent, the user input is "is the skill working"), it just doesn't work.
TestIntent's code is the same as LaunchRequest, except the intent type&name check
const request = handlerInput.requestEnvelope.request;
return (request.type === "IntentRequest" &&
request.intent.name === "TestIntent");
The alexa skill's json input recognizes the input as a TestIntent
"request": {
"type": "IntentRequest",
"requestId": "amzn1.echo-api.request.601d2e89-71c1-417e-b878-790afc6f79f4",
"timestamp": "2019-08-12T07:01:38Z",
"locale": "en-US",
"intent": {
"name": "TestIntent",
"confirmationStatus": "NONE"
},
"dialogState": "STARTED"
}
But the response is just "I am sorry, but I do not know that. Can you repeat that?"
You need to create your custom intent with utterances.
Login to https://developer.amazon.com
Create your skill and add your utterances which will map to specific intent.
Sample:
{
"interactionModel": {
"languageModel": {
"invocationName": "mySkill",
"intents": [
{
"name": "TestIntent",
"slots": [
{
"name": "name",
"type": ""
}
],
"samples": [
"test me", // This would be your utterance to identify intent
"testing you" // You can have multiple
]
},
{
"name": "AMAZON.FallbackIntent",
"samples": []
},
{
"name": "AMAZON.HelpIntent",
"samples": []
},
{
"name": "AMAZON.NoIntent",
"samples": []
}
]
}
}
}
Below is the walkthrough to the developer account
1) Create your intent
2) Create your utterances
And then just build your modal. Your Skill needs to be linked to your lambda function.
Hope this help!
======UPDATE=====
Need to return card response
response = {
outputSpeech: {
type: "PlainText",
text: output
},
card: {
type: "Simple",
title: title,
content: output
},
shouldEndSession: shouldEndSession
};
Using aw-sdk: (Sample)
return handlerInput.responseBuilder
.speak(speechText)
.reprompt(speechText)
.withSimpleCard('Hello World', speechText)
.getResponse();
}

How to handle tap action on adaptive card bot framework v4?

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!

Resources