Bot Framework v3 Dialog returning Cards - botframework

I am digging through all the great new stuff in v3 of the bot framework. One of the things that I am trying to do is create a dialog that responds with cards. But I cannot find a sample that shows how to do this.
I've tried to monkey with it on my own but haven't had much luck. In most of their code samples for Dialogs you cast the Activity object you get in your Post to an IMessageActivity class. Then when you respond you do so with just plain text. All the examples with cards have you create an Activity class. However because I've cast it to IMessageActivity I can't use the CreateReply method. And if I can't do that then when I create an Activity I get an error that the 'activityId' cannot be null.
Any advice, thoughts, or insight would be greatly appreciated.
Thanks in advance!

I added this code to my dialog:
protected override async Task MessageReceived(IDialogContext context, IAwaitable<IMessageActivity> item)
{
_message = (Activity)await item;
await base.MessageReceived(context, item);
}
[field: NonSerialized()]
private Activity _message;
[LuisIntent("Ping")]
public async Task Ping(IDialogContext context, LuisResult result)
{
Activity replyToConversation = _message.CreateReply("Should go to conversation, with a carousel");
replyToConversation.Recipient = _message.From;
replyToConversation.Type = "message";
replyToConversation.AttachmentLayout = "carousel";
.
.
.
await context.PostAsync(replyToConversation);
context.Wait(MessageReceived);
}
I got it working in the emulator but not in Skype but I guess my problem is this one Rich Card attachments are not showing on web chat or Skype

Related

How do i handle Action.Submit, after user clicks, in code (TeamsToolkit C# Bot)

I started with Hello World Bot(ICommandHandler), I modified it
Now I try to process the response from the adaptive card
Already checked it out https://learn.microsoft.com/en-us/microsoftteams/platform/bots/bot-basics?tabs=csharp
I still can't understand - Where I am supposed to catch the submit action?
Adaptive Card
Since you mentioned you used Teams Toolkit C#, I assume you are using Teams Toolkit for Visual Studio.
Current Teams Toolkit and its SDK do not have built-in support for Adaptive Cards action handling. So you need to directly use Bot Framework SDK to write your own ActivityHandler to handle the card actions.
public class TeamsBot : ActivityHandler
{
protected override async Task<InvokeResponse> OnInvokeActivityAsync(ITurnContext<IInvokeActivity> turnContext, CancellationToken cancellationToken)
{
// Handle card action
if (turnContext.Activity.Name == "adaptiveCard/action")
{
// Handle your action response
var cardJson = await File.ReadAllTextAsync("Resources/ActionResponseCard.json", cancellationToken).ConfigureAwait(false);
var response = JObject.Parse(cardJson);
var adaptiveCardResponse = new AdaptiveCardInvokeResponse()
{
StatusCode = 200,
Type = "application/vnd.microsoft.card.adaptive",
Value = response
};
return CreateInvokeResponse(adaptiveCardResponse);
}
return CreateInvokeResponse(null);
}
}
You also need to use Action.Execute instead of Action.Submit which is the newer Universal Actions for Adaptive Cards. It is a unified action model across platforms.
Also see this GitHub issue to learn more.
You can checkout this more complete example in teams samples repo.

How to have dynamic flow for teams bot built using microsoft bot framework?

I have created a Microsoft teams app which consists of tabs and a bot. I took this as reference for creating teams addon. Here I am using the Waterfall flow model which was suggested by bot framework. While using this I have to give fixed number of actions, but I wanted to have dynamic actions. here is the example
class MainDialog extends ComponentDialog {
constructor(luisRecognizer, bookingDialog) {
super('MainDialog');
if (!luisRecognizer) throw new Error('[MainDialog]: Missing parameter \'luisRecognizer\' is required');
this.luisRecognizer = luisRecognizer;
if (!bookingDialog) throw new Error('[MainDialog]: Missing parameter \'bookingDialog\' is required');
// Define the main dialog and its related components.
// This is a sample "book a flight" dialog.
this.addDialog(new TextPrompt(TEXT_PROMPT))
.addDialog(bookingDialog)
.addDialog(nominationDialogue)
.addDialog(new ChoicePrompt(CHOICE_PROMPT))
.addDialog(new WaterfallDialog(MAIN_WATERFALL_DIALOG, [
this.introStep.bind(this),
this.decidestep.bind(this),
this.originStep.bind(this),
this.actStep.bind(this),
this.Actualstep.bind(this)
]));
this.initialDialogId = MAIN_WATERFALL_DIALOG;
}
/**
* The run method handles the incoming activity (in the form of a TurnContext) and passes it through the dialog system.
* If no dialog is active, it will start the default dialog.
* #param {*} turnContext
* #param {*} accessor
*/
async run(turnContext, accessor) {
const dialogSet = new DialogSet(accessor);
dialogSet.add(this);
const dialogContext = await dialogSet.createContext(turnContext);
const results = await dialogContext.continueDialog();
if (results && results.status === DialogTurnStatus.empty) {
await dialogContext.beginDialog(this.id);
}
}
How can I go to previous function i.e, actStep to decidesStep again without having any problem. I tried calling the decideStep from actStep then I am having an exception and bot is failing. When there is some repetitive work to be done I am not able to do due to the fixed number of actions.
Thanks in advance.
I had the same problem and I solved it using something like the following code (C#). It's not clean (kind of hack) but it works
private static async Task<DialogTurnResult> actStep (WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
.
.
.
stepContext.ActiveDialog.State["stepIndex"] = (int)stepContext.ActiveDialog.State["stepIndex"] - 2;
return await stepContext.NextAsync(null, cancellationToken);
}
Could you please look into the sample code for Conversation Bot .
You could find more sample solutions here

Calling my .NET Core Teams Bot from Angular

I have created a Teams bot in .NET Core from following the sample found here: https://github.com/microsoft/BotBuilder-Samples/tree/master/samples/csharp_dotnetcore/57.teams-conversation-bot
This is working and is running locally with ngrok. I have a controller with a route of api/messages:
[Route("api/messages")]
[ApiController]
public class BotController : ControllerBase
{
private readonly IBotFrameworkHttpAdapter Adapter;
private readonly IBot Bot;
public BotController(IBotFrameworkHttpAdapter adapter, IBot bot)
{
Adapter = adapter;
Bot = bot;
}
[HttpPost]
public async Task PostAsync()
{
// Delegate the processing of the HTTP POST to the adapter.
// The adapter will invoke the bot.
await Adapter.ProcessAsync(Request, Response, Bot);
}
}
I now want to call a POST to api/messages from my Angular client using TypeScript to send a proactive message to a specific Teams user.
I did figure out how to set the ConversationParameters in TeamsConversationBot.cs to a specific Teams user by doing the following:
var conversationParameters = new ConversationParameters
{
IsGroup = false,
Bot = turnContext.Activity.Recipient,
Members = new[] { new ChannelAccount("[insert unique Teams user guid here]") },
TenantId = turnContext.Activity.Conversation.TenantId,
};
but what I'm struggling with is how to build a JSON request that sends the Teams user guid (and maybe a couple other details) to my api/messages route from TypeScript.
How do I go about doing this? What parameters/body do I need to send? I haven't been able to find samples online that show how to do this.
Update below for added clarification
I am building a web chat app using Angular for our customers. What I'm trying to do is send a proactive message to our internal employees, who are using Microsoft Teams, when a customer performs some action via the chat app (initiates a conversation, sends a message, etc.).
I've built a Teams bot using .NET Core using this sample: https://kutt.it/ZCftjJ. Modifiying that sample, I can hardcode my Teams user ID and the proactive message is showing up successfully in Teams:
var proactiveMessage = MessageFactory.Text($"This is a proactive message.");
var conversationParameters = new ConversationParameters
{
IsGroup = false,
Bot = turnContext.Activity.Recipient,
Members = new[] { new ChannelAccount("insert Teams ID here") },
TenantId = turnContext.Activity.Conversation.TenantId,
};
await ((BotFrameworkAdapter)turnContext.Adapter).CreateConversationAsync(teamsChannelId, serviceUrl, credentials, conversationParameters,
async (t1, c1) =>
{
conversationReference = t1.Activity.GetConversationReference();
await ((BotFrameworkAdapter)turnContext.Adapter).ContinueConversationAsync(_appId, conversationReference,
async (t2, c2) =>
{
await t2.SendActivityAsync(proactiveMessage, c2);
},
cancellationToken);
},
cancellationToken);
What I'm struggling with is:
How to configure my Angular app to notify my bot of a new proactive message I want to send.
How to configure the bot to accept some custom parameters (Teams user ID, message).
It sounds like you've got some progress with pro-active messaging already. Is it working 100%? If not, I've covered the topic a few times here on stack overflow - here's an example that might help: Programmatically sending a message to a bot in Microsoft Teams
However, with regards -trigging- the pro-active message, the truth is you can do it from anywhere/in any way. For instance, I have Azure Functions that run on their own schedules, and pro-active send messages as if they're from the bot, even though the code isn't running inside the bot at all. You haven't fully described where the Angular app fits into the picture (like who's using it for what), but as an example in your scenario, you could create another endpoint inside your bot controller, and do the work inside there directly (e.g. add something like below:)
[HttpPost]
public async Task ProActiveMessage([FromQuery]string conversationId)
{
//retrieve conversation details by id from storage (e.g. database)
//send pro-active message
//respond with something back to the Angular client
}
hope that helps,
Hilton's answer is still good, but the part about proactively messaging them without prior interaction requires too long of a response. So, responding to your latest comments:
Yes, the bot needs to be installed for whatever team the user resides in that you want to proactively message. It won't have permissions to do so, otherwise.
You don't need to override OnMembersAddedAsync; just query the roster (see below).
You don't need a conversation ID to do this. I'd make your API, instead, accept their Teams ID. You can get this by querying the Teams Roster, which you'll need to do in advance and store in a hash table or something...maybe a database if your team size is sufficiently large.
As far as required information, you need enough to build the ConversationParameters:
var conversationParameters = new ConversationParameters
{
IsGroup = false,
Bot = turnContext.Activity.Recipient,
Members = new ChannelAccount[] { teamMember },
TenantId = turnContext.Activity.Conversation.TenantId,
};
...which you then use to CreateConversationAsync:
await ((BotFrameworkAdapter)turnContext.Adapter).CreateConversationAsync(
teamsChannelId,
serviceUrl,
credentials,
conversationParameters,
async (t1, c1) =>
{
conversationReference = t1.Activity.GetConversationReference();
await ((BotFrameworkAdapter)turnContext.Adapter).ContinueConversationAsync(
_appId,
conversationReference,
async (t2, c2) =>
{
await t2.SendActivityAsync(proactiveMessage, c2);
},
cancellationToken);
},
cancellationToken);
Yes, you can modify that sample. It returns a Bad Request because only a particular schema is allowed on /api/messages. You'll need to add your own endpoint. Here's an example of NotifyController, which one of our other samples uses. You can see that it accepts GET requests. You'd just need to modify that our build your own that accepts POST requests.
All of this being said, all of this seems like it may be a bigger task than you're ready for. Nothing wrong with that; that's how we learn. Instead of jumping straight into this, I'd start with:
Get the Proactive Sample working and dig through the code until you really understand how the API part works.
Get the Teams Sample working, then try to make it message individual users.
Then build your bot that messages users without prior interaction.
If you run into trouble feel free to browse my answers. I've answered similar questions to this, a lot. Be aware, however, that we've switched from the Teams Middleware that I mention in some of my answers to something more integrated into the SDK. Our Teams Samples (samples 50-60) show how to do just about everything.

welcome message duplicated in Facebook channel

I'm using the following pattern used in multiple samples and it works fine in WebChat and Emulator channels but when I connected my bot to the Facebook channel it sends a duplicate welcome message.
protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
var welcomeText = "Hello and welcome!";
foreach (var member in membersAdded)
{
if (member.Id != turnContext.Activity.Recipient.Id)
{
await turnContext.SendActivityAsync(MessageFactory.Text(welcomeText, welcomeText), cancellationToken);
}
}
}
Any ideas?
I haven't worked a lot with the OnMembersAdded, so I also still have some missing understanding there, and I haven't worked with facebook bots at all, so this is just a simple guess in case it can help - it might be that facebook is sending multiple users in the membersadded list in this case. Have you tried debugging and seeing what comes through? I would guess that possibly you don't need to do a "turnContext.SendActivityAsync" in every loop of the foreach - maybe just do a check if you need to send any message (i.e into a boolean) and then just send a single one after the loop if the boolean is true.
It seem to be a bug in the bot framework.
I was able to workaround it with changed condition:
if (!member.Id.EndsWith(turnContext.Activity.Recipient.Id))
The bug is reported here
https://github.com/microsoft/BotFramework-Services/issues/165

Microsoft Bot Framework: Exception: The data is changed

I have a bot with the following conversation scenario:
Send text to LUIS
LUIS intent calls context.Call(...) to launch a Dialog
This dialog terminates, save some info in the userData:
private static async Task storeBotData(IDialogContext context, BotData userData)
{
Activity activity = (Activity)context.Activity;
StateClient sc = activity.GetStateClient();
await sc.BotState.SetUserDataAsync(activity.ChannelId, activity.From.Id, userData);
}
And after it call another dialog, again with context.Call(...).
Then the last dialog runs and terminates.
My problem is that when updating the user data at the end of the first dialog (step 3), I have the following exception in the Bot Framework Channel Emulator:
`Exception: The data is changed [File of type 'text/plain']`...
What happens here ? I think that when a dialog terminates, it call setUserData by itself, but I don't understand why I can't update userData anywhere in the code...
I have tried to catch the exception, but nothing is catched.. But I know that the userData is updated, because when I try to retrieve it back, it is updated...
Any help is welcome :)
Thanks
Botframework restores/saves state of conversation after each act of activity, so under the covers typical flow looks like the following:
[23:15:40] <- GET 200 getUserData
[23:15:47] <- GET 200 getConversationData
[23:15:47] <- GET 200 getPrivateConversationData
...
[23:16:42] <- POST 200 setConversationData
[23:16:42] <- POST 200 setUserData
[23:16:42] <- POST 200 setPrivateConversationData
As it is mentioned here : These botData objects will fail to be stored if another instance of your bot has changed the object already. So in your case the exception occurs at the termination of dialog, when framework calls setUserData by himself and figures out that the BotData has been changed already (by your explicit call of BotState.SetUserDataAsync). I suppose that's why you were not able to catch the exception.
Solution:
I used the following code and it fixed the issue:
private static void storeBotData(IDialogContext context, BotData userData)
{
var data = context.UserData;
data.SetValue("field_name", false);
}
The reason it works is that we modify the object of UserData but allow botFramework to "commit" it himself, so there is no conflict
I agree with #Artem (this solved my issue too, thanks!). I would just add the following guideline.
Use
var data = context.UserData;
data.SetValue("field_name", false);
whenever you have a IDialogContext object available, so you let the Bot Framework commit changes.
Use instead
StateClient sc = activity.GetStateClient();
await sc.BotState.SetUserDataAsync(activity.ChannelId, activity.From.Id, userData);
when you don't have an IDialogContext object, e.g. in the MessageController class.

Resources