I made a bot with bot framework v4, using C#, and it's on a webpage, https://websitebotv2.azurewebsites.net/, if there's only 1 user it works fine but the moment I open it on a new tab it gives a IndexOutOfRangeException when I start the conversation.
What do I need to do to make it work with multiple tabs open?
When my bot stars it creates a waterfall dialog asking the name and greeting the user:
public dialogBotBot(dialogBotAccessors accessors, LuisRecognizer luis, QnAMaker qna)
{
// Set the _accessors
_accessors = accessors ?? throw new ArgumentNullException(nameof(accessors));
// The DialogSet needs a DialogState accessor, it will call it when it has a turn context.
_dialogs = new DialogSet(accessors.ConversationDialogState);
// This array defines how the Waterfall will execute.
var waterfallSteps = new WaterfallStep[] {
NameStepAsync,
NameConfirmStepAsync,
};
// The incoming luis variable is the LUIS Recognizer we added above.
this.Recognizer = luis ?? throw new System.ArgumentNullException(nameof(luis));
// The incoming QnA variable is the QnAMaker we added above.
this.QnA = qna ?? throw new System.ArgumentNullException(nameof(qna));
// Add named dialogs to the DialogSet. These names are saved in the dialog state.
_dialogs.Add(new WaterfallDialog("details", waterfallSteps));
_dialogs.Add(new TextPrompt("name"));
}
Then I will save his name on UserProfile class, which contains the field Name and Context, the Context has the purpose of saving the conversation.
This works the first time, but if I open a new tab or refresh the current tab for a new conversation the bot will fetch the first conversation data.
The Exception is thrown in Startup.cs in:
services.AddBot<dialogBotBot>(options =>
{
options.CredentialProvider = new ConfigurationCredentialProvider(Configuration);
// Catches any errors that occur during a conversation turn and logs them to currently
// configured ILogger.
ILogger logger = _loggerFactory.CreateLogger<dialogBotBot>();
options.OnTurnError = async (context, exception) =>
{
logger.LogError($"Exception caught : {exception}");
await context.SendActivityAsync(exception + "\nSorry, it looks like something went wrong.\n" + exception.Message);
};
// Create and add conversation state.
var conversationState = new ConversationState(dataStore);
options.State.Add(conversationState);
// Create and add user state.
var userState = new UserState(dataStore);
options.State.Add(userState);
});
My onTurnAsync method is:
public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
// Handle Message activity type, which is the main activity type for shown within a conversational interface
// Message activities may contain text, speech, interactive cards, and binary or unknown attachments.
// see https://aka.ms/about-bot-activity-message to learn more about the message and other activity types
if (turnContext.Activity.Type == ActivityTypes.Message)
{
//Get the current user profile
userProfile = await _accessors.UserProfile.GetAsync(turnContext, () => new UserProfile(), cancellationToken);
userProfile.Contexto.Add(turnContext.Activity.Text);
foreach (string s in userProfile.Contexto)
await turnContext.SendActivityAsync(s);
// Get the conversation state from the turn context.
var state = await _accessors.CounterState.GetAsync(turnContext, () => new CounterState());
// Bump the turn count for this conversation.
state.TurnCount++;
// Check LUIS model
var recognizerResult = await this.Recognizer.RecognizeAsync(turnContext, cancellationToken);
var topIntent = recognizerResult?.GetTopScoringIntent();
// Get the Intent as a string
string strIntent = (topIntent != null) ? topIntent.Value.intent : "";
// Get the IntentScore as a double
double dblIntentScore = (topIntent != null) ? topIntent.Value.score : 0.0;
// Only proceed with LUIS if there is an Intent
// and the score for the Intent is greater than 95
if (strIntent != "" && (dblIntentScore > 2))
{
switch (strIntent)
{
case "None":
//add the bot response to contexto
await turnContext.SendActivityAsync("Desculpa, não percebi.");
break;
case "Utilities_Help":
//add the bot response to contexto
await turnContext.SendActivityAsync("Quero-te ajudar!\nO que precisas?");
break;
default:
// Received an intent we didn't expect, so send its name and score.
//add the bot response to contexto
await turnContext.SendActivityAsync($"Intent: {topIntent.Value.intent} ({topIntent.Value.score}).");
break;
}
}
else
{
if (userProfile.Name == null)
{
// Run the DialogSet - let the framework identify the current state of the dialog from the dialog stack and figure out what (if any) is the active dialog.
var dialogContext = await _dialogs.CreateContextAsync(turnContext, cancellationToken);
var results = await dialogContext.ContinueDialogAsync(cancellationToken);
// If the DialogTurnStatus is Empty we should start a new dialog.
if (results.Status == DialogTurnStatus.Empty)
{
await dialogContext.BeginDialogAsync("details", null, cancellationToken);
}
}
else
{
var answers = await this.QnA.GetAnswersAsync(turnContext);
if (answers.Any() && answers[0].Score > 0.7)
{
// If the service produced one or more answers, send the first one.
await turnContext.SendActivityAsync(answers[0].Answer + "\n" + state.TurnCount);
}
else
{
var responseMessage = $"Ainda não sei a resposta mas vou averiguar\nPosso-te ajudar com mais alguma coisa?";
String connectionString = "Data Source=botdataserverv1.database.windows.net;" +
"Initial Catalog=botDataBase;" +
"User [email protected];" +
"Password=admin_123;";
SqlConnection connection = new SqlConnection(connectionString);
SqlDataAdapter adapter = new SqlDataAdapter();
SqlCommand command;
String sms = turnContext.Activity.Text;
float result = answers[0].Score;
String insertMessage = "insert into Mensagem(texto,contexto,grauCerteza)" +
"values('" + sms + "', 'Falta apurar o contexto' ," + result + ")";
connection.Open();
command = new SqlCommand(insertMessage, connection);
adapter.InsertCommand = new SqlCommand(insertMessage, connection);
adapter.InsertCommand.ExecuteNonQuery();
command.Dispose();
connection.Close();
await turnContext.SendActivityAsync(responseMessage);
}
}
// Save the user profile updates into the user state.
await _accessors.UserState.SaveChangesAsync(turnContext, false, cancellationToken);
// Set the property using the accessor.
await _accessors.CounterState.SetAsync(turnContext, state);
// Save the new turn count into the conversation state.
await _accessors.ConversationState.SaveChangesAsync(turnContext);
}
}
}
Your problem is here:
float result = answers[0].Score;
You have:
// Check to see if we have any answers
if (answers.Any() && answers[0].Score > 0.7)
{
[...]
// This is fine
}
else // else, WE HAVE NO ANSWERS
{
[...]
// At this point, answers is an empty array, so answers[0] throws an IndexOutOfRangeException
float result = answers[0].Score;
The reason this happens on refresh is because the new tab's user uses the same User Id. The bot already knows their name
, so doesn't show the dialog, and when it calls await this.Recognizer.RecognizeAsync(turnContext, cancellationToken);
, the user hasn't entered anything in the new turnContext
, so it returns an empty array.
Sidenote: You can set the userID in WebChat with this:
window.WebChat.renderWebChat(
{
directLine: directLine,
userID: "USER_ID" // Make is use Math.random() or something if you want it to be random for each refresh
},
this.botWindowElement.nativeElement
);