I'm using QNA maker in my chatbot project using Bot Framework and I want to take the question from the adaptive card and send it to the QNA maker but I'm getting an error says: Argument 1: cannot convert from 'string' to 'Microsoft.Bot.Builder.ITurnContext'
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default)
{
var value = turnContext.Activity.Value;
if (value != null)
{
var val = value.ToString();
var relationsJSON = JToken.Parse(val);
var text = relationsJSON["text"].ToString();
if (text == "car model")
{
var result = await QnAMaker.GetAnswersAsync(text);
await turnContext.SendActivityAsync(result[0].Answer, cancellationToken: cancellationToken);
}
}
}
the error message on: var result = await QnAMaker.GetAnswerAsnyc(text);
The QnAMaker.GetAnswerAsnyc method is always expecting the ITurnContext as a mandatory parameter. So you can't inject any other parameter in the respective method.
So try the below code here JSON text is assigned to turnContext.Activity.Text object. Because as per your code OnTurnAsync method is always validating turnContext.Activity.Value object.
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default)
{
var value = turnContext.Activity.Value;
if (value != null)
{
var val = value.ToString();
var relationsJSON = JToken.Parse(val);
turnContext.Activity.Text = relationsJSON["text"].ToString();
if (turnContext.Activity.Text == "car model")
{
var result = await QnAMaker.GetAnswersAsync(turnContext.Activity.Text);
await turnContext.SendActivityAsync(result[0].Answer, cancellationToken: cancellationToken);
}
}
}
Related
This question is kind of an extension to this one, I have updated my code below to reflect a task that runs continuously updating web clients using through a Websocket connection, the tasks checks for stock prices for a provided company (symbol) received in the Recv() method, when the user changes the company the Recv() method should cancel the running task (for the older company) and start a new one for the newly provided company/symbol.
The problem I'm having is that the line where await Task.Run(() => StockPricingAsync(jsonResult), priceToken.Token); in the Recv method gets executed doesn't cancel the existing task although there is a priceToken.Cancel just right before. Any idea how to get the running task cancelled and start the same one immediately?
I have the priceToken variable declared globally so it can be accessed from within the running task, but this does not work.
I also tried to assign the task to variable and make that variable = null but that did not cancel the task as well.
I'm also open to change the approach completely if I have to, all I need is a running task that provides pricing information for a passed parameter which gets changed by the client while the connection is still open.
CancellationTokenSource priceToken = new CancellationTokenSource();
public async Task GetDataAsync(WebSocket ws)
{
CancellationToken token = new();
try
{
var sendTask = Task.Run(() => Send(ws, token));
var recvTask = Task.Run(() => Recv(ws, token));
do
{
await Task.Delay(1000);
} while (ws.State == WebSocketState.Open);
}
finally
{
await ws.CloseAsync(WebSocketCloseStatus.Empty, "", token);
}
}
async Task Recv(WebSocket ws, CancellationToken token)
{
Console.WriteLine("Recv task started...");
var buffer = WebSocket.CreateClientBuffer(1024, 1024);
WebSocketReceiveResult taskResult;
while (ws.State == WebSocketState.Open)
{
string jsonResult = "";
do
{
taskResult = await ws.ReceiveAsync(buffer, token);
jsonResult += Encoding.UTF8.GetString(buffer.Array, 0, taskResult.Count);
} while (!taskResult.EndOfMessage);
if (!string.IsNullOrEmpty(jsonResult))
{
Console.WriteLine("Queueing {0}", jsonResult);
priceToken.Cancel()
priceToken = new CancellationTokenSource();
await Task.Run(() => StockPricingAsync(jsonResult), priceToken.Token);
}
}
Console.WriteLine("Recv task exiting...");
}
async static Task Send(WebSocket ws, CancellationToken token)
{
Console.WriteLine("Send task started...");
do
{
string sendMsg = sendQueue.Take();
Console.WriteLine("Sending {0}", sendMsg);
var sendMsgBytes = Encoding.UTF8.GetBytes(sendMsg);
ArraySegment<byte> segmentBuffer = new ArraySegment<byte>(sendMsgBytes, 0, sendMsgBytes.Length);
await ws.SendAsync(segmentBuffer, WebSocketMessageType.Text, true, token);
} while (ws.State == WebSocketState.Open);
Console.WriteLine("Send task exiting...");
}
public async Task StockPricingAsync(string symbol)
{
var lastUpdated = DateTime.MinValue;
double previousPrice = 0;
new StockService().GetStockPricing(symbol, true);
while (!priceToken.Token.IsCancellationRequested)
{
var price = new StockService().GetStockPricing(symbol, false);
if (price != null && lastUpdated != price.LastPriceDate)
{
lastUpdated = price.LastPriceDate;
if (price.LastPrice > previousPrice)
price.Tick = Stock.Tick.UpTick;
else if (price.LastPrice < previousPrice)
price.Tick = Stock.Tick.DownTick;
else
price.Tick = Stock.Tick.NoChange;
previousPrice = price.LastPrice;
var json = JsonConvert.SerializeObject(new ServerData(new KeyValuePair<string, object>("StockPricing", price), _eventMessage), _jsonSerializerSettings);
sendQueue.Add(json);
}
await Task.Delay(3000, this.priceToken.Token);
}
if (this.priceToken.Token.IsCancellationRequested)
this.priceToken.Token.ThrowIfCancellationRequested();
}
My code returns Task<List<...>>.
There is a type conversion error from Task<List<...>> to List<...>.
Tell me, please, where I did not finish await ?
public async Task<List<DepartamentsResponse>> Handle(GetDepartmentsRequest token, CancellationToken cancellationToken)
{
var departments = await _departmentApiClient.GetReception(token.accessToken, OdpgDepartmentType.Reception);
var result = departments.ConvertAll(async d => new DepartamentsResponse
{
FederalDistrict = GetFederalDistrictCode(d.FederalRegion.DistrictCode),
SubjectName = d.Name,
Supervisor = GetDirector(d.Users.Where(u => u.InOdpgRole(OdpgUserRole.Director)).FirstOrDefault()),
ContactsSupervisor = GetContacts(d.Users.Where(u => u.InOdpgRole(OdpgUserRole.Director)).FirstOrDefault()),
Schedule = "C 9:00 18:00",
ReceptionContacts = await GetAddressAsync(d.Addresses.FirstOrDefault(d => d.AddressType == DepartmentAddressType.Reception), token)
});
return result;
}
private async Task<string> GetAddressAsync(DepartmentAddressDto? address, GetDepartmentsRequest token)
{
if (address != null)
{
var fullAddress = await _fiasApiClient.GetFullAddress(token.accessToken,
new ESOG.Fias.Api.Model.GetFullAddressRequest
{ BaseAddressId = address.FiasId, Building = address.Building, Room = address.Room });
//var res = JsonConvert.DeserializeObject<DepartmentAddress>(fullAddress);
return fullAddress;
}
return "";
}
GetFederalDistrictCode, GetDirector, GetContacts - these methods are not asynchronous
It should just return a List<>, not Task<List<>>
Your call to departments.ConvertAll is returning a List<Task<DepartamentsResponse>>, but you want a Task<List<DepartamentsResponse>>.
Look at the Task.WhenAll method for how you would convert a collection of Tasks to a single Task that returns a collection. Then await that single Task and build your final list.
using suggestedactions in waterstepcontext
private static async Task PromptOptions3(string prompt, string optionA, string optionB, string optionC, WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var reply = stepContext.Context.Activity.CreateReply(prompt);
reply.SuggestedActions = new SuggestedActions()
{
Actions = new List<CardAction>()
{
new CardAction() { Title = optionA, Value = optionA },
new CardAction() { Title = optionB, Value = optionB },
new CardAction() { Title = optionC, Value = optionC },
},
};
await stepContext.Context.SendActivityAsync(reply, cancellationToken);
}
private async Task<DialogTurnResult> PromptForRequestStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
// Save name, if prompted.
var greetingState = await UserProfileAccessor.GetAsync(stepContext.Context);
var lowerCaseName = stepContext.Result as string;
if (string.IsNullOrWhiteSpace(greetingState.Name) && lowerCaseName != null)
{
// Capitalize and set name.
greetingState.Name = char.ToUpper(lowerCaseName[0]) + lowerCaseName.Substring(1);
await UserProfileAccessor.SetAsync(stepContext.Context, greetingState);
}
if (greetingState.Request == "1")
{
var opts = new PromptOptions
{
Prompt = new Activity
{
Type = ActivityTypes.Message,
Text = "please work"
},
};
await PromptOptions3("Choose from the following:", "Login to OneDrive", "Upload a file", "Create a folder", stepContext, cancellationToken);
return await stepContext.PromptAsync(OneDrivePrompt, opts);
}
The suggested actions do not show up. I expected it to show up and the user can just click it as an input instead of typing. It worked when i tried it in a simpler code not in waterfallstep.. I have no idea how to fix this as I am not familar with the bot framework.
So, I don't know what kind of prompt your OneDrivePrompt is right now, but I'm going to guess that it's not a ChoicePrompt and, frankly, that's what you really want here because it will do all the work of presenting a set of options and making sure a person chooses one of them.
First, you want to change your OneDrivePrompt to a ChoicePrompt configured like so:
yourDialogSet.Add(new ChoicePrompt(OneDrivePrompt) { Style = ListStyle.SuggestedAction });
Next you'll want to change your waterfall step to work with the ChoicePrompt specifically to let it present the options and validate that one of them is chosen:
private async Task<DialogTurnResult> PromptForRequestStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
// Save name, if prompted.
var greetingState = await UserProfileAccessor.GetAsync(stepContext.Context);
var lowerCaseName = stepContext.Result as string;
if (string.IsNullOrWhiteSpace(greetingState.Name) && lowerCaseName != null)
{
// Capitalize and set name.
greetingState.Name = char.ToUpper(lowerCaseName[0]) + lowerCaseName.Substring(1);
await UserProfileAccessor.SetAsync(stepContext.Context, greetingState);
}
if (greetingState.Request == "1")
{
var opts = new PromptOptions
{
Prompt = MessageFactory.Text("Choose from the following:")
Choices = new List<Choice>
{
new Choice("Login to OneDrive"),
new Choice("Upload a file"),
new Choice("Create a folder"),
},
};
return await stepContext.PromptAsync(OneDrivePrompt, opts);
}
Hi I'm using C# in Visual studio with Microsoft's bot emulator and I'm wondering how can I prompt the user for a number input in the first message and store it in the conversation. Here's what I have so far in my MessagesController:
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
await Conversation.SendAsync(activity, () => new Dialogs.LuisHandler());
}
else
if (activity.Type == ActivityTypes.ConversationUpdate)
{
if (activity.MembersAdded.Any(o => o.Id == activity.Recipient.Id))
{
PromptDialog.Number(activity,setUserID,"Please enter your number code","Error please enter the number again",3,"",0,999);
}
}
else
{
HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
This is obviously not the correct way to do it since both the activity and setUserId attributes display an error, how can I fix this?
Also, this my SetUserId method:
public void setUserID(int id, Activity act)
{
IDialogContext cxt = act;
cxt.UserData.SetValue("userId", id);
}
Where id will be the number provided by the user and I save it in the context of the conversation to use it in queries later, how can I achieve this behavior?
how can I prompt the user for a number input in the first message and store it in the conversation.
The PromptDialog.Number() method accepts IDialogContext object as a parameter, so we can not directly call it from Messages controller (outside of Dialog) to prompt for a number.
public static void Number(IDialogContext context, ResumeAfter<long> resume, string prompt, string retry = null, int attempts = 3, string speak = null, long? min = null, long? max = null);
To achieve the requirement: prompting the user for a number input in the first message and storing it in the conversation, you can try the following sample as an workaround.
In Messages controller:
//...
else if (message.Type == ActivityTypes.ConversationUpdate)
{
// Handle conversation state changes, like members being added and removed
// Use Activity.MembersAdded and Activity.MembersRemoved and Activity.Action for info
// Not available in all channels
IConversationUpdateActivity update = message;
var client = new ConnectorClient(new System.Uri(message.ServiceUrl), new MicrosoftAppCredentials());
if (update.MembersAdded != null && update.MembersAdded.Any())
{
foreach (var newMember in update.MembersAdded)
{
if (newMember.Id != message.Recipient.Id)
{
var reply = message.CreateReply();
reply.Text = $"Welcome {newMember.Name}! Please enter your number code!";
client.Conversations.ReplyToActivityAsync(reply);
}
}
}
}
In dialog:
[Serializable]
public class RootDialog : IDialog<object>
{
bool thenumberisentered = false;
public Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
return Task.CompletedTask;
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
var activity = await result as Activity;
if (!thenumberisentered)
{
string pattern = #"^(?:0|[1-9]\d{0,2})$";
System.Text.RegularExpressions.Regex rgx = new System.Text.RegularExpressions.Regex(pattern);
if (rgx.IsMatch(activity.Text))
{
//save the number code as user data here
thenumberisentered = true;
await context.PostAsync($"The number code you entered is: {activity.Text}");
}
else
{
PromptDialog.Number(context, setUserID, "Please enter your number code", "Error please enter the number again", 2, "", 0, 999);
return;
}
}
else
{
await context.PostAsync($"You sent {activity.Text}");
}
context.Wait(MessageReceivedAsync);
}
private async Task setUserID(IDialogContext context, IAwaitable<long> result)
{
long numcode = await result;
await context.PostAsync($"The number code you entered is: {numcode}");
thenumberisentered = true;
//save the number code as user data here
context.Wait(MessageReceivedAsync);
}
}
Test result:
I'm in the middle of developing a C# discord bot and I have been successful with making the bot respond to phrases starting with "c! " and responding with the correct command, however I want the bot to reply with a GIF if the bot is mentioned. If someone could help explain why this does not work and how to fix it that would be nice. This is my code right now:
private async Task HandleCommandAsync(SocketMessage arg)
{
var message = arg as SocketUserMessage;
if (message is null || message.Author.IsBot) return;
int argPos = 0;
if (message.HasStringPrefix("", ref argPos))
{
var context = new SocketCommandContext(_client, message);
var result = await _commands.ExecuteAsync(context, argPos, _services);
if (!result.IsSuccess)
Console.WriteLine(result.ErrorReason);
}
if (message.HasMentionPrefix(_client.CurrentUser, ref argPos))
{
var embed = new EmbedBuilder();
embed.WithImageUrl("https://cdn.discordapp.com/attachments/138522037181349888/438774275546152960/Ping_Discordapp_GIF-downsized_large.gif");
await ReplyAsync("", false, embed.Build());
}
I agree with what Solarcloud said about seperating your modules but this is all you had to modify your code into:
private async Task HandleCommandAsync(SocketMessage arg)
{
var message = arg as SocketUserMessage;
if (message is null || message.Author.IsBot) return;
int argPos = 0;
if (message.HasStringPrefix("", ref argPos))
{
var context = new SocketCommandContext(_client, message);
var result = await _commands.ExecuteAsync(context, argPos, _services);
if (!result.IsSuccess)
Console.WriteLine(result.ErrorReason);
}
if (message.content.contains(_client.CurrentUser.Mention.Replace("!", "")))
{
var embed = new EmbedBuilder();
embed.WithImageUrl("https://cdn.discordapp.com/attachments/138522037181349888/438774275546152960/Ping_Discordapp_GIF-downsized_large.gif");
await ReplyAsync("", false, embed.Build());
}
The replace is just in case your bot's ID returns #<!123456789>, getting rid of it works properly.
It may be better to create a Modules folder and add your commands in a separate .cs file. To do that, you need to add this line of code after you initiate your _client but before you call LoginAsync.
_client.MessageReceived += HandleCommandAsync;
await _commands.AddModulesAsync(Assembly.GetEntryAssembly());
Then, create a seperate new class. Your command can accept a SocketGuildUser as a parameter. In chat type !help #name to call the command:
public class Help : ModuleBase<SocketCommandContext>
{
[Command("help")]
public async Task HelpAsync(SocketGuildUser user)
{
await ReplyAsync($"{user.Mention} needs a lot of help!");
}
}
Your HandleCommandAsync should look like this:
private async Task HandleCommandAsync(SocketMessage arg)
{
var message = arg as SocketUserMessage;
if (message == null || message.Author.IsBot) return;
int argPos = 0;
if (message.HasStringPrefix("!", ref argPos) ||
message.HasMentionPrefix(_client.CurrentUser, ref argPos))
{
var context = new SocketCommandContext(_client, message);
var result = await _commands.ExecuteAsync(context, argPos, _services);
if (!result.IsSuccess)
{
Console.WriteLine(result.ErrorReason);
}
}
}