Say example when we have 3 menu( choices ) within a waterfall dialog in an initial step, when user selected third choice, the system should take over to qna bot form that , is that something possible ?
Nodejs here instead of C# but hopefully this will point you in the right direction. You can do this, you just need to create a separate QnA Dialog and call it from within your waterfall via await step.beginDialog(YOUR_QNA_DIALOG_NAME). You would want that dialog to prompt for the question in the first step, provide the answer in the second step, and (if desired) prompt if they want to ask another question so you can loop via replaceDialog. If there are steps after the initial menu selection, you may wish to cancellAllDialogs instead of endDialog when they exit, otherwise the bot will pick up where it left off with the first dialog.
There might be a more elegant way to do this without using a waterfall dialog but I haven't found it yet.
I have provided a sample QnA Dialog below, but note that in this case I am using it as a default action if no intent is recognized, so it's using the activity.text from the ActivityHandler instead of explicitly prompting the user, and it does not loop. Still, I though this might be helpful.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
const { Dialog, MessageFactory } = require('botbuilder');
const { QnAServiceHelper } = require('../helpers/qnAServiceHelper');
const { CardHelper} = require('../helpers/cardHelper');
class QnADialog {
constructor() {
}
async processAsync(oldState, activity){
const defaultAnswer = `I'm sorry, I don't know how to help with that. Try asking a different question or type "Help" for options.`;
var MINIMUM_SCORE = 50;
var newState = null;
var query = activity.text;
var qnaResult = await QnAServiceHelper.queryQnAService(query, oldState);
var qnaAnswer = qnaResult[0].answer;
var prompts = null;
if(qnaResult[0].context != null){
prompts = qnaResult[0].context.prompts;
}
var outputActivity = null;
if (prompts == null || prompts.length < 1) {
if (qnaResult[0].score > MINIMUM_SCORE) {
outputActivity = MessageFactory.text(qnaAnswer);
} else {
outputActivity = MessageFactory.text(defaultAnswer);
}
}
else {
var newState = {
PreviousQnaId: qnaResult[0].id,
PreviousUserQuery: query
}
outputActivity = CardHelper.GetHeroCard('', qnaAnswer, prompts);
}
return [newState, outputActivity , null];
}
}
module.exports.QnADialog = QnADialog;
Related
I have an issue in which i'm looking for our discord bot to only look for images received and ignore any text typed.
From the guides I have read, I have yet to come across any that hasn't required a command.
I have tried to use a command with no command within the string, however it doesn't build as it doesn't contain a parameter.
Does anyone have any ideas how I can just listen for an image only?
Below is an example of my code.
private async Task _client_MessageReceived(SocketMessage arg)
{
var message = arg as SocketUserMessage;
var context = new SocketCommandContext(_client, message);
if (message.Author.IsBot) return;
int argPos = 0;
if (message.HasStringPrefix("!", ref argPos) || message.Attachments.Count > 0)
{
var result = await _commands.ExecuteAsync(context, argPos, _services);
if (!result.IsSuccess) Console.WriteLine(result.ErrorReason);
}
else
await message.DeleteAsync();
}
[Command("")]
public async Task Photo()
{
var attachments = Context.Message.Attachments;
WebClient myWebClient = new WebClient();
string file = attachments.ElementAt(0).Filename;
string url = attachments.ElementAt(0).Url;
myWebClient.DownloadFile(url, #"mydirect");
_ = Task.Run(async () =>
{
AWS.AWS.Get_kv_map(#"mydirect");
});
}
my sugestion is to check if message.Attachments != 0 ==> do your thing, with that you can check if it has any Attachments is on it and after that you can check if its ending on an .jpg or .png or so.
example:
if(message.Attachments.Count != 0){
var image attachements = message.Attachments.Where(x =>
x.Filename.EndsWith(".jpg") || x.Filename.EndsWith(".png") ||
x.Filename.EndsWith(".gif")); // or what you want as "image"
if(image.Any()){
// do your stuff from your method Photo() here or just call here your method your decision
}else{
// ignore or whatever you want to do it with it
}
I hope it helped and good luck on your project :D
var image = message.Attachments.Where(x => x.Filename.EndsWith("*.png") ...);
This code will be one of the great solution but If you want to check that's real image then.
First, download image.
Second check magic number.
https://en.wikipedia.org/wiki/List_of_file_signatures
This wikipedia link has list of magic number, for example, MZ means PE file.
If byte of file not starts with MZ, windows will deny of execution.
I have actually two issues.
First : I would like to reprompt the user input within an case.intent (which is in a waterfall).
I use var value = ((string)stepContext.Result); but it just store the input already given by the user instead of asking a new one.
Second : then i would like to pass the value into another class : cards.cs, to be then rendered in an adaptive card. I would like to use it instead of "Mickey Mouse" here in cards.cs
How can i store the variable from one class to another. I come from a python background, can I use a global variable in C#?
In maindialog.cs I wrote the following.
case FlightBooking.Intent.order:
var modifiermessagetext = "What is your order";
var value = ((string)stepContext.Result);
var modifiermessage = MessageFactory.Text(modifiermessagetext, modifiermessagetext, InputHints.IgnoringInput);
var messageText70 = stepContext.Options?.ToString() ?? "you can contact the customer service at 098789876";
var promptMessage70 = MessageFactory.Text(messageText70, messageText70, InputHints.ExpectingInput);
await stepContext.Context.SendActivityAsync(modifiermessage, cancellationToken);
var attachments70 = new List<Attachment>();
var reply70 = MessageFactory.Attachment(attachments70);
reply70.Attachments.Add(Cards.CardJson());
await stepContext.Context.SendActivityAsync(reply70, cancellationToken);
return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = promptMessage70 }, cancellationToken);
In Cards.cs:
public static Attachment CardJson()
{
var templateJson = #"
{
""type"": ""AdaptiveCard"",
""version"": ""1.0"",
""body"": [
{
""type"": ""TextBlock"",
""text"": ""Hello {name}""
}
]
}";
var dataJson = #"
{
""name"": ""Mickey Mouse""
}";
var transformer = new AdaptiveTransformer();
var cardJson = transformer.Transform(templateJson, dataJson);
var adaptiveCardAttachment = new Attachment()
{
ContentType = "application/vnd.microsoft.card.adaptive",
Content = JsonConvert.DeserializeObject(cardJson),
};
return adaptiveCardAttachment;
}
I am using the new feature of adaptive cards : https://learn.microsoft.com/en-us/adaptive-cards/templating/sdk
Is there a simple way to do this ? Any suggestion would be welcome. Thanks!
First let me explain how a waterfall dialog works
According to Microsoft Docs
A waterfall dialog is a specific implementation of a dialog that is commonly used to collect information from the user or guide the user through a series of tasks. Each step of the conversation is implemented as an asynchronous function that takes a waterfall step context (step) parameter. At each step, the bot prompts the user for input (or can begin a child dialog, but that it is often a prompt), waits for a response, and then passes the result to the next step. The result of the first function is passed as an argument into the next function, and so on.
The following diagram shows a sequence of waterfall steps and the stack operations that take place.
That means when you prompt a dialog in a waterfall step, you can get that result in the NEXT step of the waterfall not within the same step as you have tried.
stepContext.Result: gets the result from the Previous waterfall step
stepContext.Options: contains input information for the dialog. It gets any options the waterfall dialog was called with.
For example lets say you did await context.BeginDialogAsync("yourWaterfallDialog", false); "false" is the Options that can be fetched from your waterfall step like this
if (sc.Options != null && sc.Options is bool)
{
valid = (bool)sc.Options;
}
stepContext.Values: contains information you can add to the context, and is carried forward into subsequent steps. Its a dictionary of values which will be persisted across all waterfall steps Example: stepContext.Values["name"] = "Marc"
Take a look at this example:
private async Task<DialogTurnResult> StepOne(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions Prompt = promptMessage70 }, cancellationToken);
}
private async Task<DialogTurnResult> StepTwo(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
// Get the user response from StepOne:
var value = ((string)stepContext.Result);
// we pass the value to CardJson
reply70.Attachments.Add(Cards.CardJson(value));
await stepContext.Context.SendActivityAsync(reply70,cancellationToken);
}
As you can see, in StepTwo we fetched the result of the user input from StepOne.
To get the Value fetched instead of "Mickey Mouse" in your adaptive card you can simply pass it as a string parameter like so Cards.CardJson(value)
public static Attachment CardJson(string value){
...
var dataJson = #"{'name': '" + value + "'}";
...
}
BuildForm Method
public static IForm<FAQConversation> BuildForm()
{
return new FormBuilder<FAQConversation>()
.Field(new FieldReflector<FAQConversation>(nameof(Inquiry))
.SetValidate(AnswerInquiry)
.SetPrompt(new PromptAttribute("Okay, tell me what is your question. Enter \"back\" to go back to Products Selection."))
)
.Build();
}
Validation Method
private static async Task<ValidateResult> AnswerInquiry(FAQConversation state, object value)
{
var result = new ValidateResult();
//somecode here
if(testCase == false)
{
result.isValid = false;
result.Feedback = "Try again";
}
else
{
result.isValid = true;
}
return result;
}
My validation method returns the feedback "Try Again" text when the input on my validating field is invalid. However, it is returning both Original Prompt and the Feedback text.
Question
How do I remove the original prompt on revalidation of a field?
While FormFlow does offer a lot of customizability, the main idea behind it is to automate everything for you, which tends to indicate that at least some things are built in pretty strongly.
I understand that what you want to do is disable the prompt for a field upon "retry," which is to say that if the user was already shown the prompt for a field and they entered something invalid then they shouldn't be shown the prompt again. I can see in the source code that FormFlow doesn't really provide a special case for "retries" and the behavior of prompting when a field remains unknown is one of those built-in things. However, there is still something you can do.
FormFlow offers a (largely undocumented) way to replace what's called the "prompter." You can do this using the Prompter() method, which takes a PromptAsyncDelegate. As a starting point for your new prompter, you can find the default prompter in the FormBuilder source code:
_form._prompter = async (context, prompt, state, field) =>
{
var preamble = context.MakeMessage();
var promptMessage = context.MakeMessage();
if (prompt.GenerateMessages(preamble, promptMessage))
{
await context.PostAsync(preamble);
}
await context.PostAsync(promptMessage);
return prompt;
};
Whereas the default prompter always posts promptMessage, your replacement can surround that line with an if statement. That leaves the question of what your condition should be. We've established that FormFlow doesn't include any concept of a retry, so you'd have to build that in yourself somehow. You could include a Boolean field as a switch in FAQConversation's state, or you could even use PrivateConversationData since the prompter gives you access to the DialogContext. You might think that would be a simple matter of turning off the switch when the prompt gets displayed once or when AnswerInquiryAsync determines that the user input is invalid, but then when would the switch get turned back on? What if the user enters "back" and you want the prompt to be displayed again?
While you might find some way to more accurately represent the logic of "disabling the prompt on retry," the simplest solution I came up with was to keep track of the last message FormFlow produced and then skip the first message that comes after "Try again." It looks like this:
[Serializable]
public class FAQConversation
{
public string Inquiry { get; set; }
private string LastMessage { get; set; }
private const string TRY_AGAIN = "Try again";
public static IForm<FAQConversation> BuildForm()
{
return new FormBuilder<FAQConversation>()
// This is an alternative way of using the Field() method but it works the same.
.Field(nameof(Inquiry),
"Okay, tell me what is your question. Enter \"back\" to go back to Products Selection.",
validate: AnswerInquiryAsync)
.Prompter(PromptAsync)
.Build();
}
private static async Task<ValidateResult> AnswerInquiryAsync(FAQConversation state, object value)
{
var result = new ValidateResult();
bool testCase = Equals(value, "true"); // Enter "true" to continue for testing purposes.
if (testCase == false)
{
result.IsValid = false;
// A constant should be used with strings that appear more than once in your code.
result.Feedback = TRY_AGAIN;
}
else
{
result.IsValid = true;
// A value must be provided or else the Field will not be populated.
result.Value = value;
}
return result;
}
/// <summary>
/// Here is the method we're using for the PromptAsyncDelegate.
/// </summary>
private static async Task<FormPrompt> PromptAsync(IDialogContext context, FormPrompt prompt,
FAQConversation state, IField<FAQConversation> field)
{
var preamble = context.MakeMessage();
var promptMessage = context.MakeMessage();
if (prompt.GenerateMessages(preamble, promptMessage))
{
await context.PostAsync(preamble);
}
// Here is where we've made a change to the default prompter.
if (state.LastMessage != TRY_AGAIN)
{
await context.PostAsync(promptMessage);
}
state.LastMessage = promptMessage.Text;
return prompt;
}
}
I am using FormFlow with enums to render some questions but it seems that formflow is rendering them as a HeroCard with buttons, I'd like the prompts to render as suggested actions so the show as quick replies in FB, what would be the best way of doing this?
For now, I implemented a custom prompter as follows but like to know if there's a better way of doing this with attributes so I don't need to write custom code.
private static async Task<FormPrompt> Prompter(IDialogContext context, FormPrompt prompt, JObject state, IField<JObject> field)
{
IMessageActivity promptMessage;
// Handle buttons as quick replies when possible (FB only renders 11 quick replies)
if (prompt.Buttons.Count > 0 && prompt.Buttons.Count <= 11)
{
// Build a standard prompt with suggested actions.
promptMessage = context.MakeMessage();
promptMessage.Text = prompt.Prompt;
var actions = prompt.Buttons.Select(button => new CardAction
{
Type = ActionTypes.ImBack,
Title = button.Description,
Value = button.Description
})
.ToList();
promptMessage.SuggestedActions = new SuggestedActions(actions: actions);
}
else
{
promptMessage = await Extensions.GetDefaultPrompter(context, prompt);
}
await context.PostAsync(promptMessage);
return prompt;
}
If you want this functionality than you will have to stick to your implementation. Formflow tries to be as abstract as possible and presenting the enum options as a Herocard with buttons is just a result of that. It does this because almost all channels support Herocards and only facebook supports quick replies.
I have created an app with in app purchase and tested it with CurrentAppSimulator and works fine but when i create the package for app it fails
This is my code for which i am creating package
public async System.Threading.Tasks.Task InAppInit()
{
var listing = await CurrentApp.LoadListingInformationAsync();
// Delux Unlock - Durable
var unlockFeatureDelux = listing.ProductListings.FirstOrDefault(p => p.Value.ProductId == "deluxe" && p.Value.ProductType == ProductType.Durable);
isDeluxPurchased = CurrentApp.LicenseInformation.ProductLicenses[unlockFeatureDelux.Value.ProductId].IsActive;
deluxProductID = unlockFeatureDelux.Value.ProductId;
// Standard Unlock - Durable
var unlockFeatureStandard = listing.ProductListings.FirstOrDefault(p => p.Value.ProductId == "standard" && p.Value.ProductType == ProductType.Durable);
isStarndardPurchased = CurrentApp.LicenseInformation.ProductLicenses[unlockFeatureStandard.Value.ProductId].IsActive;
standardProductID = unlockFeatureStandard.Value.ProductId;
}
I am calling this method OnLaunched in App.xaml.cs
Based on our discussion here is what I would do:
The LoadListingInformationAsync method uses internet and if the user does not have an internet connection then it will throw an exception. So I suggest to wrap this whole stuff into a try/ctach block
As we see the ProductListings does not contain any item. I don't know how this list is populated, but as long as your app is not in the store I would not be surprised when that list is empty (Maybe someone can help out here... but i did not find anything regarding this in the docs). So for this I would just simply check if the feature you need is in the list... With this your package will pass the test you mentioned and you can upload it. If the list is also empty when the package is installed via the store, then something with the IAP setting is wrong (but that is related to the store..)
And a general comment: Obviously this code is not complete... You need some code for purchasing the IAPs and here we only get the Ids.. (But I think you only pasted the relevant part anyway.)
So all this in code:
public async System.Threading.Tasks.Task InAppInit()
{
try
{
var listing = await CurrentApp.LoadListingInformationAsync();
if (listing.ProductListings.ContainsKey("deluxe"))
{
// Delux Unlock - Durable
var unlockFeatureDelux = listing.ProductListings.FirstOrDefault(p => p.Value.ProductId == "deluxe" && p.Value.ProductType == ProductType.Durable);
isDeluxPurchased = CurrentApp.LicenseInformation.ProductLicenses[unlockFeatureDelux.Value.ProductId].IsActive;
deluxProductID = unlockFeatureDelux.Value.ProductId;
}
else
{
//There is no deluxe IAP defined... so something with your IAP stuff is wrong...
}
if (listing.ProductListings.ContainsKey("standard"))
{
// Standard Unlock - Durable
var unlockFeatureStandard = listing.ProductListings.FirstOrDefault(p => p.Value.ProductId == "standard" && p.Value.ProductType == ProductType.Durable);
isStarndardPurchased = CurrentApp.LicenseInformation.ProductLicenses[unlockFeatureStandard.Value.ProductId].IsActive;
standardProductID = unlockFeatureStandard.Value.ProductId;
}
else
{
//same as for Delux
}
}
catch
{
//Show this on the UI...
}
}