I am trying to use the fluent API to create a simple flow. But instead of using plain text I want to use rich visual components. Here is an example.
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
var yn = Chain
.PostToChain()
.Select(m => CreateYesNoPrompt(activity)) //This is a dialog which should provide some buttons to the user
.PostToUser()
.WaitToBot()
.Select(x => x.Text)
.Switch
(
Chain.Case
(
s => s == "S",
new ContextualSelector<string, IDialog<string>>((context, item) => Chain.Return("Yes"))
),
Chain.Default<string, IDialog<string>>((context, text) => Chain.Return("No"))
)
.Unwrap()
.PostToUser();
await Conversation.SendAsync(activity, () => yn);
return Request.CreateResponse(HttpStatusCode.OK);
}
private static Activity CreateYesNoPrompt(Activity activity)
{
var reply = activity.CreateReply();
var ybutton = new CardAction(type: "postBack", title: "Yes", value: "S");
var nbutton = new CardAction(type: "postBack", title: "No", value: "N");
var buttons = new List<CardAction>() { ybutton, nbutton };
var card = new HeroCard("Would you like to start an order?", "Subtitle", buttons: buttons);
reply.Attachments = new List<Attachment> { card.ToAttachment() };
return reply;
}
Instead of the expected output, the bot is outputting Microsoft.Bot.Connector.Activity, which is the ToString() return of the Activity object.
How to I use cards within dialogs?
This shows a card when passed a DialogContext:
private static Activity ShowButtons(IDialogContext context, string strText)
{
// Create a reply Activity
Activity replyToConversation = (Activity)context.MakeMessage();
replyToConversation.Text = strText;
replyToConversation.Recipient = replyToConversation.Recipient;
replyToConversation.Type = "message";
// Call the CreateButtons utility method
// that will create 5 buttons to put on the Here Card
List<CardAction> cardButtons = CreateButtons();
// Create a Hero Card and add the buttons
HeroCard plCard = new HeroCard()
{
Buttons = cardButtons
};
// Create an Attachment
// set the AttachmentLayout as 'list'
Attachment plAttachment = plCard.ToAttachment();
replyToConversation.Attachments.Add(plAttachment);
replyToConversation.AttachmentLayout = "list";
// Return the reply to the calling method
return replyToConversation;
}
See:
Using Images, Cards, Carousels, and Buttons In The Microsoft Bot Framework
Related
Hi i tried all these methods of attaching a video to a bot. All of them are working fine in bot emulator. But when i publish it to messenger it is throwing an exception . (I can't see the exception by the way i just know because of the message. Is there a way to see or log exceptions?). Is video card not supported in messenger? Or is youtube not supported as an url link?
Here are the codes:
AddStep(async (stepContext, cancellationToken) =>
{
var reply = stepContext.Context.Activity.CreateReply();
reply.Attachments = new List<Attachment>();
reply.Attachments.Add(GetVideoCard().ToAttachment());
await stepContext.Context.SendActivityAsync(reply, cancellationToken);
return await stepContext.NextAsync();
});
////////////////
private static VideoCard GetVideoCard()
{
var videoCard = new VideoCard
{
Title = "Budgeting Introduction",
Subtitle = "by Finko",
Media = new List<MediaUrl>
{
new MediaUrl()
{
Url = "https://www.youtube.com/watch?v=XLo1geVokhA",
},
},
Buttons = new List<CardAction>
{
new CardAction()
{
Title = "Learn More at Finko.PH",
Type = ActionTypes.OpenUrl,
Value = "https://m-moreno.wixsite.com/finkoph?fbclid=IwAR1NVtlyKfzZ0mYFIWva8L-d8TUv4KFpt_m1i1ij3raT-pbWr2c3-kqzB2Q",
},
},
};
return videoCard;
}
and
AddStep(async (stepContext, cancellationToken) =>
{
var activity = stepContext.Context.Activity;
await stepContext.Context.SendActivityAsync(CreateResponse(activity, CreateVideoCardAttacment()));
return await stepContext.NextAsync();
});
////////////////////////
private Activity CreateResponse(Activity activity, Attachment attachment)
{
var response = activity.CreateReply();
response.Attachments = new List<Attachment>() { attachment };
return response;
}
private Attachment CreateVideoCardAttacment()
{
return new VideoCard()
{
Title = "Are you a Seafarer? OFW? FREE PERSONAL FINANCIAL ADVICE HERE!!",
Media = new List<MediaUrl>()
{
new MediaUrl("https://www.youtube.com/watch?v=XLo1geVokhA")
},
Buttons = new List<CardAction>()
{
new CardAction()
{
Type = ActionTypes.OpenUrl,
Title = "Learn More at Finko.PH",
Value = "https://m-moreno.wixsite.com/finkoph?fbclid=IwAR1NVtlyKfzZ0mYFIWva8L-d8TUv4KFpt_m1i1ij3raT-pbWr2c3-kqzB2Q"
}
},
Subtitle = "by Finko.Ph",
Text = "Are you tired of getting bogus financial advice? Tired of having 'kape' just to find out it was networking, or a pyramid scheme? Tired of scouring the internet for HOURS but not finding what you're looking for? We're here to help! We give financial advice and will educate you on financial literacy topics, ABSOLUTELY FREE!!"
}.ToAttachment();
}
and
Activity reply = stepContext.Context.Activity.CreateReply();
var card = new VideoCard
{
Title = "Finko.ph",
Media = new List<MediaUrl>()
{
new MediaUrl("https://www.youtube.com/watch?v=XLo1geVokhA")
},
Buttons = new List<CardAction>()
{
new CardAction()
{
Type = ActionTypes.OpenUrl,
Title = "Learn More at Finko.PH",
Value = "https://m-moreno.wixsite.com/finkoph?fbclid=IwAR1NVtlyKfzZ0mYFIWva8L-d8TUv4KFpt_m1i1ij3raT-pbWr2c3-kqzB2Q"
}
},
};
reply.Attachments.Add(card.ToAttachment());
await stepContext.Context.SendActivityAsync(reply);
return await stepContext.NextAsync();
and
var reply1 = stepContext.Context.Activity.CreateReply();
var attachment1 = new Attachment
{
ContentUrl = "https://www.youtube.com/watch?v=XLo1geVokhA",
ContentType = "video/mp4",
Name = "imageName1",
};
reply1.Attachments = new List<Attachment>() { attachment1 };
await stepContext.Context.SendActivityAsync(reply1, cancellationToken);
return await stepContext.NextAsync();
All of these codes are working in bot emulator but not in messenger. Any help would be appreciated thank you.
The BotFramework converts Video Cards into Media Templates for Facebook Messenger, and per Facebook's Developer documentation, media templates do not allow any external URLs, only those on Facebook. You must either upload the video to Facebook or provide a URL directly to the mp4 file which, unfortunately, YouTube doesn't make readily available.
For more details, take a look at Facebooks documentation regarding Media Templates.
Within a bot,we have an adaptive card where the user has a choice to select yes or no.
On selecting YES, user is prompted to enter the keywords.
After the user gives input in the textblock in adaptive card, the input has to be captured and sent as input parameter to web api.
The user input will be given in Placeholder of the AdaptiveTextInput block.
public static Attachment GetUserInputForCustomPPT()
{
AdaptiveCard card = new AdaptiveCard()
{
Id = "GetCustomPPT",
Body = new List<AdaptiveElement>()
{
new AdaptiveTextBlock()
{
Text = "Do you want to apply filter and customise the PPT?",
Wrap=true,
Size = AdaptiveTextSize.Small
},
new AdaptiveContainer()
{
Id = "getCustomPPTNo",
SelectAction = new AdaptiveSubmitAction()
{
Id = "getCustomPPTNo",
Title = "No",
DataJson = "{ \"Type\": \"GetCustomPPT\" }",
}
},
new AdaptiveContainer()
{
Id = "getCustomPPTYes",
Items = new List<AdaptiveElement>()
{
new AdaptiveTextBlock()
{
Text = "Please select an option",
Wrap=true,
Size = AdaptiveTextSize.Small
}
}
},
},
Actions = new List<AdaptiveAction>()
{
new AdaptiveShowCardAction()
{
Id = "GetPPTYes",
Title = "Yes",
Card = new AdaptiveCard()
{
Body = new List<AdaptiveElement>()
{
new AdaptiveTextBlock()
{
Text = "Please enter your input",
Wrap = true
},
new AdaptiveTextInput()
{
Id="GetUserInputKeywords",
Placeholder="Please enter the keyword list separated by ',' Ex:RPA,FS ",
MaxLength=490,
IsMultiline=true
}
},
Actions = new List<AdaptiveAction>()
{
new AdaptiveSubmitAction()
{
Id = "contactSubmit",
Title = "Submit",
DataJson = "{ \"Type\": \"GetPPT\" }"
},
new AdaptiveOpenUrlAction()
{
Id="CallApi",
Url=new Uri("https://xyz"+"RPA")
//card.Actions.Card.AdaptiveTextInput.Placeholder
}
}
}
},
new AdaptiveShowCardAction()
{
Id = "GetPPTNo",
Title = "No",
Card = new AdaptiveCard()
{
Body = new List<AdaptiveElement>()
{
},
Actions = new List<AdaptiveAction>()
{
new AdaptiveSubmitAction()
{
Id = "contactSubmit",
Title = "Submit",
DataJson = "{ \"Type\": \"GetPPTNo\" }"
}
}
}
}
}
};
// Create the attachment with adapative card.
Attachment attachment = new Attachment()
{
ContentType = AdaptiveCard.ContentType,
Content = card
};
return attachment;
}
Yes, you can retrieve the input values from an AdaptiveCard and use them as parameters in an HTTP request to an API. When the user submits an AdaptiveCard, the input fields are sent to the bot through the activity in the Value attribute. You can parse the resulting JSON string with JObject and retrieve the values for your API call. See the example below.
public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
if (turnContext.Activity.Type == ActivityTypes.Message)
{
// Check if user submitted AdaptiveCard input
if (turnContext.Activity.Value != null) {
// Convert String to JObject
String value = turnContext.Activity.Value.ToString();
JObject results = JObject.Parse(value);
// Get type from input field
String name = results.GetValue("Type").ToString();
// Get Keywords from input field
String userInputKeywords = "";
if (name == "GetPPT") {
userInputKeywords = results.GetValue("GetUserInputKeywords").ToString();
}
// Make Http request to api with paramaters
String myUrl = $"http://myurl.com/api/{userInputKeywords}";
...
// Respond to user
await turnContext.SendActivityAsync("Respond to user", cancellationToken: cancellationToken);
} else {
// Send user AdaptiveCard
var cardAttachment = GetUserInputForCustomPPT();
var reply = turnContext.Activity.CreateReply();
reply.Attachments = new List<Attachment>() { cardAttachment };
await turnContext.SendActivityAsync(reply, cancellationToken);
}
}
}
Hope this helps!
Using routing you can pass multiple parameters either on the route itself or pass parameters on the query string, via Model Binding or content value binding. For most common scenarios this actually works very well. As long as you are passing either a single complex type via a POST operation, or multiple simple types via query string or POST buffer, there's no issue. But if you need to pass multiple parameters as was easily done with WCF REST or ASP.NET AJAX things are not so obvious.
RouteTable.Routes.MapHttpRoute(
name: "ApiName",
routeTemplate: "photos/**{action}**/{title}",
defaults: new {
title = RouteParameter.Optional,
controller = "PhotoApi",
**action =** **"GetPhotos"** });
working on QnAMaker. The bot responses well with both Text and HeroCard responses on the Emulator and the WebChat. However, it is not sending the formatted HeroCard via my channel(Skype). I have rebuilt the application using build.cmd. Restarted the Azure services. Still no help.
// BasicQnAMakerDialog.cs:
public RootDialog() : base(new QnAMakerService
(new QnAMakerAttribute(
RootDialog.GetSetting("QnAAuthKey") == null ? ConfigurationManager.AppSettings["QnAAuthKey"] : ConfigurationManager.AppSettings["QnAAuthKey"],
Utils.GetAppSetting("QnAKnowledgebaseId") == null ? ConfigurationManager.AppSettings["QnAKnowledgebaseId"] : ConfigurationManager.AppSettings["QnAKnowledgebaseId"],
"Not sure how to help with that. Try asking about the ALOTB, Pricing, Technology and scope to find more info or get in touch with sriram.chandrasekaran#epsilon.com", 0.5, 1,
Utils.GetAppSetting("QnAEndpointHostName") == null ? ConfigurationManager.AppSettings["QnAEndpointHostName"] : ConfigurationManager.AppSettings["QnAEndpointHostName"])))
{
}
//override method
protected override async Task RespondFromQnAMakerResultAsync(IDialogContext context, IMessageActivity message, QnAMakerResults result)
{
var answer = result.Answers.First().Answer;
Activity reply = ((Activity)context.Activity).CreateReply();
string[] qnaAnswerData = answer.Split(';');
int dataSize = qnaAnswerData.Length;
if (dataSize > 1 && dataSize <= 6)
{
var attachment = GetSelectedCard(answer);
reply.Attachments.Add(attachment as Attachment);
await context.PostAsync(reply);
}
else
{
await context.Forward(new BasicQnAMakerDialog(), AfterAnswerAsync, message, CancellationToken.None);
await context.PostAsync(reply);
}
}
private static Attachment GetSelectedCard(string answer)
{
int len = answer.Split(';').Length;
switch (len)
{
case 4: return GetHeroCard(answer);
default: return GetHeroCard(answer);
}
}
private static Attachment GetHeroCard(string answer)
{
string[] qnaAnswerData = answer.Split(';');
string title = qnaAnswerData[0];
string description = qnaAnswerData[1];
string url = qnaAnswerData[2];
string imageURL = qnaAnswerData[3];
HeroCard card = new HeroCard
{
Title = title,
Subtitle = description,
};
card.Buttons = new List<CardAction>
{
new CardAction(ActionTypes.OpenUrl, "Learn More", value: url)
};
card.Images = new List<CardImage>
{
new CardImage( url = imageURL)
};
return card.ToAttachment();
}
I have created a carousel using Hero Cards. It works fine in bot framework emulator but in Facebook messenger it shows only my first reply "Select an option" of the code below? Am I missing something, does messenger supports carousel? Why are the images and buttons missing?
Activity replyToConversation = activity.CreateReply("Select an option");
replyToConversation.AttachmentLayout = AttachmentLayoutTypes.Carousel;
replyToConversation.Attachments = new List<Attachment>();
Dictionary<string, string> cardContentList = new Dictionary<string, string>();
cardContentList.Add("Shirt", System.Web.HttpContext.Current.Server.MapPath(#"~\imgs\shirt.jpg"));
cardContentList.Add("shoes", System.Web.HttpContext.Current.Server.MapPath(#"~\imgs\shoes.jpg"));
foreach (KeyValuePair<string, string> cardContent in cardContentList)
{
List<CardImage> cardImages = new List<CardImage>();
cardImages.Add(new CardImage(url: cardContent.Value));
List<CardAction> cardButtons = new List<CardAction>();
CardAction plButton = new CardAction()
{
Value = "nike",
Type = "postBack",
Title = "shirt"
};
cardButtons.Add(plButton);
HeroCard plCard = new HeroCard()
{
Title = "nike",
Images = cardImages,
Buttons = cardButtons
};
Attachment plAttachment = plCard.ToAttachment();
replyToConversation.Attachments.Add(plAttachment);
}
await context.PostAsync(replyToConversation);
I tried to implement your case on my bot, it's working using images hosted on the web, but not with "local" images from your bot folder.
Using local images I got an Exception on my bot, not just "Select an option".
Code for this test:
[Serializable]
public class Dialog49665918 : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
await context.PostAsync("Type anything to get user question or 'debug' to get debug version");
context.Wait(this.MessageReceivedAsync);
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
var activity = await result as Activity;
var replyToConversation = activity.CreateReply("Select an option");
replyToConversation.AttachmentLayout = AttachmentLayoutTypes.Carousel;
replyToConversation.Attachments = new List<Attachment>();
var cardContentList = new Dictionary<string, string>();
if (!"debug".Equals(activity.Text, StringComparison.InvariantCultureIgnoreCase))
{
cardContentList.Add("Shirt", System.Web.HttpContext.Current.Server.MapPath(#"~\imgs\shirt.jpg"));
cardContentList.Add("shoes", System.Web.HttpContext.Current.Server.MapPath(#"~\imgs\shoes.jpg"));
}
else
{
cardContentList.Add("Shirt", "https://media.deparis.me/3257-tm_large_default/tshirt-homme-papa-cool-et-tatoue.jpg");
cardContentList.Add("shoes", "https://assets.adidas.com/images/w_840,h_840,f_auto,q_auto/d4dd2144b22b41bfbbd5a7ff01674bb3_9366/Superstar_Shoes_White_C77153_01_standard.jpg");
}
foreach (var cardContent in cardContentList)
{
var cardImages = new List<CardImage>
{
new CardImage(url: cardContent.Value)
};
var plButton = new CardAction()
{
Value = "nike",
Type = "postBack",
Title = "shirt"
};
var cardButtons = new List<CardAction>
{
plButton
};
var plCard = new HeroCard()
{
Title = "nike",
Images = cardImages,
Buttons = cardButtons
};
var plAttachment = plCard.ToAttachment();
replyToConversation.Attachments.Add(plAttachment);
}
await context.PostAsync(replyToConversation);
}
}
Proof ("debug" case, with internet images):
Below is my Dialog.
[Serializable]
public class EmailDialog : IDialog<object>
{
async Task IDialog<object>.StartAsync(IDialogContext context)
{
context?.Wait(RequestEmailAddress);
}
private async Task RequestEmailAddress(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var message = await result;
context.Wait(RequestEmailAddress);
var thumbNailCard = new ThumbnailCard
{
Title = "BotFramework Thumbnail Card",
Subtitle = "Your bots — wherever your users are talking",
Text = "Build and connect intelligent bots to interact with your users naturally wherever they are, from text/sms to Skype, Slack, Office 365 mail and other popular services.",
Images = new List<CardImage> { new CardImage("https://sec.ch9.ms/ch9/7ff5/e07cfef0-aa3b-40bb-9baa-7c9ef8ff7ff5/buildreactionbotframework_960.jpg") },
Buttons = new List<CardAction> { new CardAction(ActionTypes.OpenUrl, "Get Started", value: "https://docs.botframework.com/en-us/") }
};
var resultMessage = context.MakeMessage();
resultMessage.AttachmentLayout = AttachmentLayoutTypes.Carousel;
resultMessage.Attachments = new List<Attachment> { thumbNailCard.ToAttachment() };
await context.PostAsync(resultMessage);
}
}
After the card is sent, any user input is hitting the REST API endpoint and the method RequestEmailAddress is not called at all.
NOTE: Initially I forgot to add context.Wait(RequestEmailAddress); and did so only after a couple of run. Could it be the bot has already ran into infinite loop of not knowing the context?
I even tried clearing up the stack and clearing up the bot state. Nothing helped.
EDIT: Adding the message controller part
private async Task<Activity> OnHandleActivityType(Activity activity)
{
switch (activity.Type)
{
case ActivityTypes.ContactRelationUpdate:
OnContactRelationUpdate(activity);
break;
case ActivityTypes.ConversationUpdate:
OnConversationUpdate(activity);
break;
case ActivityTypes.DeleteUserData:
break;
case ActivityTypes.EndOfConversation:
break;
case ActivityTypes.Event:
break;
case ActivityTypes.Invoke:
break;
case ActivityTypes.Message:
OnMessageReceived(activity);
break;
case ActivityTypes.Ping:
break;
case ActivityTypes.Typing:
break;
default:
throw new NotImplementedException();
}
return null;
}
private async void OnMessageReceived(Activity activity)
{
var message = activity.Text;
if (message == AppConstants.RegisterEmail))
{
await Conversation.SendAsync(activity, () => new EmailDialog());
}
}
The context.Wait(RequestEmailAddress); call should be at the end of the RequestEmailAddress method.
private async Task RequestEmailAddress(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var message = await result;
var thumbNailCard = new ThumbnailCard
{
Title = "BotFramework Thumbnail Card",
Subtitle = "Your bots — wherever your users are talking",
Text = "Build and connect intelligent bots to interact with your users naturally wherever they are, from text/sms to Skype, Slack, Office 365 mail and other popular services.",
Images = new List<CardImage> { new CardImage("https://sec.ch9.ms/ch9/7ff5/e07cfef0-aa3b-40bb-9baa-7c9ef8ff7ff5/buildreactionbotframework_960.jpg") },
Buttons = new List<CardAction> { new CardAction(ActionTypes.OpenUrl, "Get Started", value: "https://docs.botframework.com/en-us/") }
};
var resultMessage = context.MakeMessage();
resultMessage.AttachmentLayout = AttachmentLayoutTypes.Carousel;
resultMessage.Attachments = new List<Attachment> { thumbNailCard.ToAttachment() };
await context.PostAsync(resultMessage);
context.Wait(RequestEmailAddress);
}
Also, incoming messages will always hit your endpoint (because it's the entry point). Then they will be delivered to the dialog. That means that you need to update the OnMessageReceived method on your controller and remove the if check for the message text match otherwise the user message won't be delivered anywhere:
private async void OnMessageReceived(Activity activity)
{
await Conversation.SendAsync(activity, () => new EmailDialog());
}