I'm trying to create some animation during the time when I fetch the data from a server. "Typing" activity seems to be reasonable but it works only for ~4 seconds :
Activity reply = activity.CreateReply();
reply.Type = ActivityTypes.Typing;
reply.Text = null;
ConnectorClient connector = new ConnectorClient(new Uri(activity.ServiceUrl));
await connector.Conversations.ReplyToActivityAsync(reply);
I was trying to do async listening:
while (!_fetchEnded)
{
await connector.Conversations.ReplyToActivityAsync(reply);
Thread.Sleep(3000);
}
But bot it creates laggy behaviour. Is there a possibility to set the duration of "typing" activity or another way around to prevent turning the typing on and off?
Typing is displayed only a few seconds by default. You can force the display typing indicator longer by sending again typing events at a lower frequency.
Implementation example, where it will send events every 2 seconds, for 30 seconds max:
public async Task<HttpResponseMessage> Post([FromBody]Microsoft.Bot.Connector.Activity activity, CancellationToken token)
{
// Send Typing messages
var typingCancellation = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var typingTask = SendTypingActivityUntilCancellation(activity, TimeSpan.FromSeconds(2), typingCancellation.Token);
try
{
// Activity treatment
if (activity.Type == ActivityTypes.Message)
{
// ...
}
else if (activity.Type == ActivityTypes.Event && activity.ChannelId == ChannelEnum.directline.ToString())
{
// ...
}
typingCancellation.Cancel();
await typingTask;
return Request.CreateResponse(HttpStatusCode.OK);
}
catch (Exception e)
{
typingCancellation.Cancel();
await typingTask;
return Request.CreateResponse(HttpStatusCode.InternalServerError);
}
}
public async Task SendTypingActivityUntilCancellation(Activity activity, TimeSpan period, CancellationToken cancellationtoken)
{
try
{
var connector = new ConnectorClient(new Uri(activity.ServiceUrl));
Activity isTypingReply = activity.CreateReply();
isTypingReply.Type = ActivityTypes.Typing;
do
{
if (cancellationtoken.IsCancellationRequested == false)
{
await connector.Conversations.ReplyToActivityAsync(isTypingReply);
}
// Check again if token has not been canceled during the reply delay
if (cancellationtoken.IsCancellationRequested == false)
{
await Task.Delay(period);
}
}
while (cancellationtoken.IsCancellationRequested == false);
}
catch (OperationCanceledException)
{
//nothing to do.
}
}
Related
This is code from my handler:
public class MoeZdravjeStatusCodeHandler : DelegatingHandler
{
public MoeZdravjeStatusCodeHandler(HttpMessageHandler innerHandler) : base(innerHandler) { }
public IMojLekDataStore<Object> MojLekDataStore => DependencyService.Get<IMojLekDataStore<Object>>();
public bool flag=true;
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpResponseMessage response = new HttpResponseMessage();
response = await base.SendAsync(request, cancellationToken);
int status_code = (int)response.StatusCode;
if (response.IsSuccessStatusCode)
{
return response;
}
else if(status_code == 401 && DependencyService.Get<ISharedFunctions>().GetUserValidation() == true )
{
try
{
if (flag)
{
flag = false;
string date = DateTime.Today.ToString("yyyy-MM-dd") + "MoeZdravje";
string password = Helpers.ServiceHelper.HashPassword(date).ToString();
LoginMoeZdravje log = new LoginMoeZdravje();
log.EZBO = DependencyService.Get<ISharedFunctions>().GetPatient().Ezbo.ToString();
log.PASSWORD = password;
log.CONFIRMED_USER = 1;
var response_token = await MojLekDataStore.GetMoeZdravjeToken(log);
if (response_token != null && !String.IsNullOrEmpty(response_token.Token))
{
flag = true;
DependencyService.Get<ISharedFunctions>().SaveMoeZdravjeToken(response_token.Token);
await Application.Current.MainPage.DisplayAlert("", "ВАШИОТ ТОКЕН Е ОСВЕЖЕН", AppResources.BTNOK);
Application.Current.MainPage = new NavigationPage(new MainMenu());
}
else
{
flag = true;
await Application.Current.MainPage.DisplayAlert("", AppResources.SERVER_ERROR, AppResources.BTNOK);
}
}
else
{
CancellationTokenSource source = new CancellationTokenSource();
cancellationToken = source.Token;
source.Cancel();
source.Dispose();
}
}
catch (AggregateException ae)
{
foreach (Exception e in ae.InnerExceptions)
{
if (e is TaskCanceledException)
Console.WriteLine("Unable to compute mean: {0}",
((TaskCanceledException)e).Message);
else
Console.WriteLine("Exception: " + e.GetType().Name);
}
}
finally
{
}
}
return response;
}
I want when come to await MojLekDataStore.GetToken(log); block every async Task until finish this request because with this request i get a new token from my Api and need to save that token and call the requests to get the data with the new token. I have a tabbedPage with 4 async Tasks and this handler called two times for get a new token but i need one and to start work with the new token.
EDIT
I added CancellationTokenSource source = new CancellationTokenSource();
but i dont know if this could stop other 3 async task ? The flag is used when first 401status_code request come .
I am trying to receive an int number (1-4) via Backchannel and then hand it over to the first dialog.
My message controller looks like this:
private int option = 1;
/// <summary>
/// POST: api/Messages
/// Receive a message from a user and reply to it
/// </summary>
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
try
{
var connector = new ConnectorClient(new Uri(activity.ServiceUrl));
Activity isTypingReply = activity.CreateReply();
isTypingReply.Type = ActivityTypes.Typing;
await connector.Conversations.ReplyToActivityAsync(isTypingReply);
await Conversation.SendAsync(activity, () => new Dialogs.MenuDialog(option));
}
catch (Exception e)
{
//SendEmail(e);
}
}
else
{
await HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
private async Task HandleSystemMessage(Activity message)
{
if (message.Type == ActivityTypes.DeleteUserData)
{
// Implement user deletion here
// If we handle user deletion, return a real message
}
else if (message.Type == ActivityTypes.ConversationUpdate)
{
}
else if (message.Type == ActivityTypes.ContactRelationUpdate)
{
// Handle add/remove from contact lists
// Activity.From + Activity.Action represent what happened
}
else if (message.Type == ActivityTypes.Typing)
{
// Handle knowing tha the user is typing
}
else if (message.Type == ActivityTypes.Ping)
{
}
else if (message.Type == ActivityTypes.Event && message.Name == "option")
{
// var reply = message.CreateReply();
//reply.Text = message.Value.ToString();
// ConnectorClient connector = new ConnectorClient(new Uri(message.ServiceUrl));
// await connector.Conversations.ReplyToActivityAsync(reply);
if (message.Value.ToString() == "1")
{
option = 1;
}
else if (message.Value.ToString() == "2")
{
option = 2;
}
else if (message.Value.ToString() == "3")
{
option = 3;
}
else if (message.Value.ToString() == "4")
{
option = 4;
}
else
{
option = 1;
}
}
return;
}
The Backchannel method is called right and the option value is set when I print it at the end of the function.
But then when the first message comes the Bot is always using the default "1" value.
It was working before but now it stopped working and I don't understand why.
private int option = 1;
Is scoped to the MessageController, and will be refreshed on every call. You can use PrivateConversationData to preserve the 'option' between the Event and Message calls:
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
//retrieve the option value before processing the message
string optionValue = string.Empty;
using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, activity))
{
var botData = scope.Resolve<IBotData>();
await botData.LoadAsync(CancellationToken.None);
optionValue = botData.PrivateConversationData.GetValue<string>("option");
}
await Conversation.SendAsync(activity, () => new ParameterizedRootDialog(optionValue));
}
else if (activity.Type == ActivityTypes.Event)
{
var eventActivity = activity.AsEventActivity();
if (string.Equals(eventActivity.Name, "option", StringComparison.InvariantCultureIgnoreCase))
{
//save the option into PrivateConversationData
string optionValue = eventActivity.Value.ToString();
using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, activity))
{
var botData = scope.Resolve<IBotData>();
await botData.LoadAsync(CancellationToken.None);
botData.PrivateConversationData.SetValue("option", optionValue);
await botData.FlushAsync(CancellationToken.None);
}
}
}
return Request.CreateResponse(HttpStatusCode.OK);
}
Also of note: with this method, it is not necessary to send in the option to the dialog as a parameter. You could retrieve the value from within the dialog itself, using IDialogContext.PrivateConversationData. Like this:
var optionFromContext = context.PrivateConversationData.GetValue<string>("option");
This code is using botframework 4
I have rootdialog which is calling an another dialog called choicedialog .From the choicedialog i am returning the DialogturnResult(waiting) from BeginDialogAsync method.After that the bot wait for the input from the user.
when the user enter something it should call the ContinueAsync Method in the ChoiceDialog.But the bot is calling the Rootdailog ContinueAsync .
what is the reason for this ?How can i solve this?
in the Controller onTurnAync Method
async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken)
{
if (turnContext.Activity.Type == ActivityTypes.Message)
{
var dc = await Dialogs.CreateContextAsync(turnContext, cancellationToken);
var dialogResult = await dc.ContinueDialogAsync();
if (!dc.Context.Responded)
{
// examine results from active dialog
switch (dialogResult.Status)
{
case DialogTurnStatus.Empty:
await dc.BeginDialogAsync(nameof(RootDialog));
break;
case DialogTurnStatus.Waiting:
// The active dialog is waiting for a response from the user, so do nothing.
break;
case DialogTurnStatus.Complete:
await dc.EndDialogAsync();
break;
default:
await dc.CancelAllDialogsAsync();
break;
}
}
}
}
rootDialog
public override async Task<DialogTurnResult> BeginDialogAsync(DialogContext context, object options = null, CancellationToken cancellationToken = default(CancellationToken))
{
var activity = context.Context.Activity;
context.SendTyping(activity);
var response = DataFromService();
if (response == null || response.StatusCode != 1)
{
await context.PostAsync(Messages.StandardErrorMessage);
}
if (response.Data != null)
{
return await dialog.BeginDialogAsync(context);
}
else
{
return new DialogTurnResult(DialogTurnStatus.Waiting);
}
}
ChoiceDialog
public override async Task<DialogTurnResult> BeginDialogAsync(DialogContext context, object options = null, CancellationToken cancellationToken = default(CancellationToken))
{
choiceStep.SaveEntityDataInContext(context: context);
IList<IMessageActivity> messages = GenerateMessageActivity(context);
if (messages.IsCollectionValid())
{
foreach (var message in messages)
{
await context.PostActivityToUser(message);
}
}
var dialogResult = new DialogTurnResult(DialogTurnStatus.Waiting);
return dialogResult;
}
I don't see you saving the changes to DialogState anywhere. Usually this is done at the end of OnTurnAsync. You could possibly be using AutoSaveStateMiddleware, but you didn't mention that (and, frankly, I wouldn't recommend it).
Specifically you need to call SaveChangesAsync on the BotState instance that you called CreateProperty<DialogState>. Usually this is ConversationState.
If you don't do this, every turn will start with an empty dialog stack, thus the behavior you're describing where the root dialog is always being run.
I'm posting two messages back to the user as a reply as below,
static Timer t = new Timer(new TimerCallback(TimerEvent));
static Timer t1 = new Timer(new TimerCallback(TimerEventInActivity));
static int timeOut = Convert.ToInt32(ConfigurationManager.AppSettings["disableEndConversationTimer"]); //3600000
public static void CallTimer(int due) {
t.Change(due, Timeout.Infinite);
}
public static void CallTimerInActivity(int due) {
t1.Change(due, Timeout.Infinite);
}
public async static Task PostAsyncWithDelay(this IDialogContext ob, string text) {
try {
var message = ob.MakeMessage();
message.Type = Microsoft.Bot.Connector.ActivityTypes.Message;
message.Text = text;
await PostAsyncWithDelay(ob, message);
CallTimer(300000);
if ("true".Equals(ConfigurationManager.AppSettings["disableEndConversation"])) {
CallTimerInActivity(timeOut);
}
} catch (Exception ex) {
Trace.TraceInformation(ex.Message);
}
}
await context.PostAsyncWithDelay("Great!");
await context.PostAsyncWithDelay("I can help you with that.");
But, there is no delay between them when received. Both messages are received in one go.
How can I delay the second message with some time?
In Root Dialog
To delay your message you can use Task.Delay method. Change your PostAsyncWithDelay as:
public async static Task PostAsyncWithDelay(IDialogContext context, string text)
{
await Task.Delay(4000).ContinueWith(t =>
{
var message = context.MakeMessage();
message.Text = text;
using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, message))
{
var client = scope.Resolve<IConnectorClient>();
client.Conversations.ReplyToActivityAsync((Activity)message);
}
});
}
You can call PostAsyncWithDelay method when you want to delay a message, otherwise use context.PostAsync method to send your messages.
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
//Sending a message nomally
await context.PostAsync("Hi");
//Notify the user that the bot is typing
var typing = context.MakeMessage();
typing.Type = ActivityTypes.Typing;
await context.PostAsync(typing);
//The message you want to delay.
//NOTE: Do not use context.PostAsyncWithDelay instead simply call the method.
await PostAsyncWithDelay(context, "2nd Hi");
}
OUTPUT
How can I delay the second message with some time?
If you’d like to delay sending the second message, you can try the following code snippet:
await context.PostAsync($"You sent {activity.Text} at {DateTime.Now}");
Task.Delay(5000).ContinueWith(t =>
{
using (var scope = Microsoft.Bot.Builder.Dialogs.Internals.DialogModule.BeginLifetimeScope(Conversation.Container, activity))
{
var client = scope.Resolve<IConnectorClient>();
Activity reply = activity.CreateReply($"I can help you with that..");
client.Conversations.ReplyToActivityAsync(reply);
}
});
context.Wait(MessageReceivedAsync);
Besides, as others mentioned in comments, the method PostAsyncWithDelay seems not a built-in method in Bot Builder SDK. If you try to achieve the requirement and defined that custom method, you can post the code of that method.
To make all replies delay, you may insert this directly in the controller.
if (activity.Type == ActivityTypes.Message)
{
var connector = new ConnectorClient(new Uri(activity.ServiceUrl));
Activity isTypingReply = activity.CreateReply();
isTypingReply.Type = ActivityTypes.Typing;
await connector.Conversations.ReplyToActivityAsync(isTypingReply);
var message = isTypingReply;
await Task.Delay(4000).ContinueWith(t =>
{
using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, message))
{
}
});
await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());
}
I am developing a chatbot using Microsoftt Bot Framework and LUIS cognitive services.
I want a initial welcome message something like "Hello user how are you!" as soon as my bot starts.
anything can be done here in MessageController
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
Trace.TraceInformation($"Type={activity.Type} Text={activity.Text}");
if (activity.Type == ActivityTypes.Message)
{
//await Microsoft.Bot.Builder.Dialogs.Conversation.SendAsync(activity, () => new ContactOneDialog());
await Microsoft.Bot.Builder.Dialogs.Conversation.SendAsync(activity, () =>
new ExceptionHandlerDialog<object>(new ShuttleBusDialog(), displayException: true));
//await Microsoft.Bot.Builder.Dialogs.Conversation.SendAsync(activity, () => new ShuttleBusDialog());
}
else
{
HandleSystemMessage(activity);
}
var response = Request.CreateResponse(System.Net.HttpStatusCode.OK);
return response;
}
You might want to explore sending the message as part of the ConversationUpdate event. Update your HandleSystemMessage method so it looks like the following:
private async Task HandleSystemMessage(Activity message)
{
if (message.Type == ActivityTypes.DeleteUserData)
{
// Implement user deletion here
// If we handle user deletion, return a real message
}
else if (message.Type == ActivityTypes.ConversationUpdate)
{
ConnectorClient client = new ConnectorClient(new Uri(message.ServiceUrl));
var reply = message.CreateReply();
reply.Text = "Hello user how are you?"
await client.Conversations.ReplyToActivityAsync(reply);
}
else if (message.Type == ActivityTypes.ContactRelationUpdate)
{
// Handle add/remove from contact lists
// Activity.From + Activity.Action represent what happened
}
else if (message.Type == ActivityTypes.Typing)
{
// Handle knowing tha the user is typing
}
else if (message.Type == ActivityTypes.Ping)
{
}
}
In the new version HandleSystemMessage is no more async and it returns an Activity, so this is what worked for me:
private Activity HandleSystemMessage(Activity message)
{
if (message.Type == ActivityTypes.DeleteUserData)
{
// Implement user deletion here
// If we handle user deletion, return a real message
}
else if (message.Type == ActivityTypes.ConversationUpdate)
{
if (message.MembersAdded.Any(o => o.Id == message.Recipient.Id))
{
ConnectorClient connector = new ConnectorClient(new Uri(message.ServiceUrl));
Activity reply = message.CreateReply("I am your service provider virtual assistant, How can I help you today? ");
connector.Conversations.ReplyToActivityAsync(reply);
}
}
else if (message.Type == ActivityTypes.ContactRelationUpdate)
{
// Handle add/remove from contact lists
// Activity.From + Activity.Action represent what happened
}
else if (message.Type == ActivityTypes.Typing)
{
// Handle knowing tha the user is typing
}
else if (message.Type == ActivityTypes.Ping)
{
}
return null;
}
Note the following things:
Since HandleSystemMessage is not async you cannot "await" to reply.
Use the recipient id check to avoid duplicated welcome messages
Why modify the HandleSystemMessage method, when you could simply handle it right in the Post method?
Thus, you don't have to mess with creating a new connector and using the unfamiliar connector.Conversations.ReplyToActivityAsync(reply) method.
You can simply start your root dialog, same as you would do in reply to a message:
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
// ... some code ...
// this will also handle the beginning of the conversation -
// that is when activity.Type equals to ConversationUpdate
if (activity.Type == ActivityTypes.Message
|| activity.Type == ActivityTypes.ConversationUpdate)
{
// Because ConversationUpdate activity is received twice
// (once for the Bot being added to the conversation, and the 2nd time -
// for the user), we have to filter one out, if we don't want the dialog
// to get started twice. Otherwise the user will receive a duplicate message.
if (activity.Type == ActivityTypes.ConversationUpdate &&
!activity.MembersAdded.Any(r => r.Name == "Bot"))
return response;
// start your root dialog here
await Microsoft.Bot.Builder.Dialogs.Conversation.SendAsync(activity, () =>
new ExceptionHandlerDialog<object>(new ShuttleBusDialog(), displayException: true));
}
// handle other types of activity here (other than Message and ConversationUpdate
// activity types)
else
{
HandleSystemMessage(activity);
}
var response = Request.CreateResponse(System.Net.HttpStatusCode.OK);
return response;
}
When I used "WebChat" channel, the below code worked fine. ChatBot could able to initiate the greeting message and also there was no duplicates!
case ActivityTypes.ConversationUpdate:
IConversationUpdateActivity update = activity;
var client = new ConnectorClient(new Uri(activity.ServiceUrl), new MicrosoftAppCredentials());
if (update.MembersAdded != null && update.MembersAdded.Any())
{
foreach (var newMember in update.MembersAdded)
{
if (newMember.Id == activity.Recipient.Id)
{
var reply = activity.CreateReply();
reply.Text = $"Welcome {newMember.Name}!";
await client.Conversations.ReplyToActivityAsync(reply);
}
}
}
break;