Created
November 18, 2018 01:38
-
-
Save nmfisher/8f8e3b1f57111c97541f74ed7ffe5621 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Lexico.DataModel; | |
using Lexico.DataModel.Questionnaire; | |
using Lexico.Search.NETStandard; | |
using Lexico.Web.Multitenancy.NET461; | |
using Lexico.Web.Multitenancy.NET461.User; | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using System.Text.RegularExpressions; | |
using Lexico.Services.Questionnaire; | |
using Lexico.Web.Questionnaire; | |
namespace Lexico.Web.Dialog | |
{ | |
public class DialogStateBuilder : IDialogStateBuilder | |
{ | |
public enum Instruction { Affirmative, Negative, Cancel, Reset, None }; | |
public static Regex HelloRequestedRegex = new Regex(@"^(hello|hi)"); | |
public static Regex CreateRegex = new Regex(@"^(create|make|new|give|generate)"); | |
public static Regex HelpRegex = new Regex(@"^(help)"); | |
public static Regex AffirmativeRegex = new Regex(@"^(yes|y|ok|confirm)"); | |
public static Regex NegativeRegex = new Regex(@"^(no|n)$"); | |
public static Regex ListRequestedRegex = new Regex(@"^(show|list)"); | |
public static Regex ArticleRegex = new Regex(@"( the | a | an | new)"); | |
public static Regex NextRegex = new Regex("(')(next)"); | |
public static Regex ResetRegex = new Regex("^(cancel|quit|reset)"); | |
protected readonly IQuestionnaireService QuestionnaireService; | |
protected readonly IApplicationUserIdentity ApplicationUserIdentity; | |
protected readonly IFuzzySearchService FuzzySearchService; | |
private readonly IWebConfigurationOptions WebConfigurationOptions; | |
private readonly IWebQuestionnaireService WebQuestionnaireService; | |
public DialogStateBuilder(IQuestionnaireService questionnaireService, | |
IFuzzySearchService fuzzySearchService, | |
IApplicationUserIdentity applicationUserIdentity, | |
IWebQuestionnaireService webQuestionnaireService, | |
IWebConfigurationOptions webConfigurationOptions) | |
{ | |
FuzzySearchService = fuzzySearchService; | |
QuestionnaireService = questionnaireService; | |
ApplicationUserIdentity = applicationUserIdentity; | |
WebConfigurationOptions = webConfigurationOptions; | |
WebQuestionnaireService = webQuestionnaireService; | |
} | |
public IDialogState Initiate() | |
{ | |
return new Null(this); | |
} | |
public Instruction Parse(string text) | |
{ | |
var lower = text.ToLower(); | |
if (AffirmativeRegex.IsMatch(lower)) | |
return Instruction.Affirmative; | |
else if (NegativeRegex.IsMatch(lower)) | |
return Instruction.Negative; | |
else if (ResetRegex.IsMatch(lower)) | |
return Instruction.Reset; | |
else | |
return Instruction.None; | |
} | |
protected string[] FormatResponse(IQuestionnaireItem questionnaireItem) | |
{ | |
var questionText = questionnaireItem.QuestionText; | |
if (questionnaireItem.Type == QuestionnaireType.UserOption) | |
{ | |
var conditions = questionnaireItem.ConditionalItems.ToList(); | |
questionText += " ["; | |
for (int i = 0; i < conditions.Count; i++) | |
{ | |
questionText += conditions[i].Condition; | |
if (i < conditions.Count - 1) | |
questionText += " / "; | |
} | |
questionText += "]"; | |
} | |
return new string[] { | |
questionText, | |
questionnaireItem.Required ? null : "This question isn't required - type 'next' to skip." | |
}; | |
} | |
internal class Null : IDialogState | |
{ | |
private readonly DialogStateBuilder DialogStateBuilder; | |
public Null(DialogStateBuilder dialogStateBuilder) | |
{ | |
DialogStateBuilder = dialogStateBuilder; | |
} | |
public void Process(IDialog dialog, string interaction) | |
{ | |
dialog.States = new Stack<IDialogState>(); | |
IDialogState nextState; | |
if (CreateRegex.IsMatch(interaction.ToLower())) | |
{ | |
nextState = new CreateRequested(DialogStateBuilder); | |
} else if(ListRequestedRegex.IsMatch(interaction.ToLower())) | |
{ | |
nextState = new ListRequested(DialogStateBuilder); | |
} else if(HelpRegex.IsMatch(interaction.ToLower())) | |
{ | |
nextState = new HelpRequested(DialogStateBuilder); | |
} else if (HelloRequestedRegex.IsMatch(interaction.ToLower())) | |
{ | |
nextState = new HelloRequested(DialogStateBuilder); | |
} | |
else | |
{ | |
nextState = new Unknown(DialogStateBuilder); | |
} | |
dialog.IsProcessing = true; | |
dialog.States.Push(nextState); | |
} | |
} | |
internal class HelloRequested : IDialogState | |
{ | |
private readonly DialogStateBuilder DialogStateBuilder; | |
public HelloRequested(DialogStateBuilder dialogStateBuilder) | |
{ | |
DialogStateBuilder = dialogStateBuilder; | |
} | |
public void Process(IDialog dialog, string interaction) | |
{ | |
dialog.States = new Stack<IDialogState>(); | |
dialog.States.Push(new Null(DialogStateBuilder)); | |
dialog.Responses = new List<dynamic>() | |
{ | |
"Hi!", | |
"Create a questionnaire by typing 'create a ' followed by the name of the questionnaire in your account", | |
"Type 'help' to get a link to our user guide.", | |
"Type 'list' to list all questionnaires in your account", | |
"You can also type 'help' to get a link to our user guide." | |
}; | |
dialog.IsProcessing = false; | |
} | |
} | |
public class ResetRequested : IDialogState | |
{ | |
private readonly DialogStateBuilder DialogStateBuilder; | |
public ResetRequested(DialogStateBuilder dialogStateBuilder) | |
{ | |
DialogStateBuilder = dialogStateBuilder; | |
} | |
public void Process(IDialog dialog, string interaction) | |
{ | |
dialog.States.Pop(); | |
dialog.States.Push(new ProcessingResetConfirmation(DialogStateBuilder)); | |
dialog.Responses = new string[] | |
{ | |
"Do you want to discard this conversation?" | |
}; | |
dialog.IsProcessing = false; | |
} | |
} | |
public class ProcessingResetConfirmation : IDialogState | |
{ | |
private readonly DialogStateBuilder DialogStateBuilder; | |
public ProcessingResetConfirmation(DialogStateBuilder dialogStateBuilder) | |
{ | |
DialogStateBuilder = dialogStateBuilder; | |
} | |
public void Process(IDialog dialog, string interaction) | |
{ | |
var instruction = DialogStateBuilder.Parse(interaction); | |
dialog.IsProcessing = true; | |
if (instruction == Instruction.Affirmative) | |
{ | |
dialog.States.Push(new ResetConfirmed(DialogStateBuilder)); | |
} else | |
{ | |
dialog.States.Pop(); | |
} | |
} | |
} | |
public class ResetConfirmed : IDialogState | |
{ | |
private readonly DialogStateBuilder DialogStateBuilder; | |
public ResetConfirmed(DialogStateBuilder dialogStateBuilder) | |
{ | |
DialogStateBuilder = dialogStateBuilder; | |
} | |
public void Process(IDialog dialog, string interaction) | |
{ | |
dialog.States = new Stack<IDialogState>(); | |
dialog.States.Push(new Null(DialogStateBuilder)); | |
dialog.Outputs = null; | |
dialog.Inputs = null; | |
dialog.Responses = new string[] | |
{ | |
"OK, I've reset everything." | |
}; | |
dialog.IsProcessing = false; | |
} | |
} | |
public class ListRequested : IDialogState | |
{ | |
private readonly DialogStateBuilder DialogStateBuilder; | |
public ListRequested(DialogStateBuilder dialogStateBuilder) | |
{ | |
DialogStateBuilder = dialogStateBuilder; | |
} | |
public void Process(IDialog dialog, string interaction) | |
{ | |
var questionnaires = DialogStateBuilder.QuestionnaireService.List(DialogStateBuilder.ApplicationUserIdentity.User, | |
DialogStateBuilder.ApplicationUserIdentity.Organization)?.ToList(); | |
var responses = new List<string>(); | |
if (questionnaires == null || questionnaires.Count == 0) | |
responses.Add("Sorry, there don't seem to be any documents available."); | |
else { | |
responses.Add("The following questionnaires are available"); | |
responses.AddRange(questionnaires.Select((x) => x.Name)); | |
} | |
dialog.Responses = responses.Cast<dynamic>().ToList(); | |
dialog.IsProcessing = false; | |
dialog.States = new Stack<IDialogState>(); | |
dialog.States.Push(new Null(DialogStateBuilder)); | |
} | |
} | |
public class HelpRequested : IDialogState | |
{ | |
private readonly DialogStateBuilder DialogStateBuilder; | |
public HelpRequested(DialogStateBuilder dialogStateBuilder) | |
{ | |
DialogStateBuilder = dialogStateBuilder; | |
} | |
public void Process(IDialog dialog, string interaction) | |
{ | |
dialog.States.Pop(); | |
dialog.States.Push(new Null(DialogStateBuilder)); | |
dialog.IsProcessing = false; | |
dialog.Responses = new List<dynamic>() | |
{ | |
"Need some help? Please visit https://app.lexico.io/help for our user guide." | |
}; | |
} | |
} | |
public class CreateRequested : IDialogState | |
{ | |
private readonly DialogStateBuilder DialogStateBuilder; | |
public CreateRequested(DialogStateBuilder dialogStateBuilder) | |
{ | |
DialogStateBuilder = dialogStateBuilder; | |
} | |
public void Process(IDialog dialog, string interaction) | |
{ | |
var target = CreateRegex.Replace(interaction, ""); | |
target = ArticleRegex.Replace(target, ""); | |
target = target.Trim(); | |
var questionnaires = DialogStateBuilder.FuzzySearchService.Find(target, | |
DialogStateBuilder.ApplicationUserIdentity.User, | |
DialogStateBuilder.ApplicationUserIdentity.Organization); | |
dialog.States = new Stack<IDialogState>(); | |
if (questionnaires == null || questionnaires.Count() == 0) | |
{ | |
dialog.States.Push(new CreateNotFound(DialogStateBuilder)); | |
} | |
else { | |
dialog.Inputs = new Stack<object>(questionnaires.Select((x) => x.Object).Reverse()); | |
dialog.States.Push(new CreateConfirmationNeeded(DialogStateBuilder)); | |
dialog.IsProcessing = true; | |
} | |
} | |
} | |
public class CreateConfirmationNeeded : IDialogState | |
{ | |
private readonly DialogStateBuilder DialogStateBuilder; | |
public CreateConfirmationNeeded(DialogStateBuilder dialogStateBuilder) | |
{ | |
DialogStateBuilder = dialogStateBuilder; | |
} | |
public void Process(IDialog dialog, string interaction) | |
{ | |
dialog.States.Pop(); | |
dialog.Responses = new List<dynamic>() | |
{ | |
"We found a " + (dialog.Inputs.Peek() as IQuestionnaire).Name + ", is this what you wanted?" | |
}; | |
dialog.States.Push(new ProcessingCreateConfirmation(DialogStateBuilder)); | |
dialog.IsProcessing = false; | |
} | |
} | |
public class ProcessingCreateConfirmation : IDialogState | |
{ | |
private readonly DialogStateBuilder DialogStateBuilder; | |
public ProcessingCreateConfirmation(DialogStateBuilder dialogStateBuilder) | |
{ | |
DialogStateBuilder = dialogStateBuilder; | |
} | |
public void Process(IDialog dialog, string interaction) | |
{ | |
var questionnaire = (IQuestionnaire)dialog.Inputs.Pop(); | |
var instruction = DialogStateBuilder.Parse(interaction); | |
if (instruction == Instruction.Cancel || instruction == Instruction.Reset) | |
{ | |
dialog.States.Push(new Null(DialogStateBuilder)); | |
dialog.Responses = new string[] { "OK, cancelled." }; | |
} | |
else if (instruction == Instruction.Affirmative) | |
{ | |
dialog.Inputs.Push(new DialogQuestionnaireState(questionnaire)); | |
dialog.Responses = new List<dynamic>() | |
{ | |
"OK, we'll kick things off here - remember you can also create this @ " + DialogStateBuilder.WebConfigurationOptions.FormatCreateTemplateUrl(questionnaire.Id) | |
}; | |
dialog.States.Push(new InputRequired(DialogStateBuilder)); | |
dialog.IsProcessing = true; | |
} | |
else if (instruction == Instruction.Negative) | |
{ | |
if (dialog.Inputs.Count == 0) | |
{ | |
dialog.States.Push(new Null(DialogStateBuilder)); | |
dialog.IsProcessing = false; | |
dialog.Responses = new string[] { "Sorry, we don't have any more documents." }; | |
} | |
else | |
{ | |
dialog.Responses = new string[] { ((IQuestionnaire)dialog.Inputs.Peek()).Name + "?" }; | |
dialog.IsProcessing = false; | |
} | |
} | |
else | |
{ | |
throw new NotSupportedException(); | |
} | |
} | |
} | |
public class CreateNotFound : IDialogState | |
{ | |
private readonly DialogStateBuilder DialogStateBuilder; | |
public CreateNotFound(DialogStateBuilder dialogStateBuilder) | |
{ | |
DialogStateBuilder = dialogStateBuilder; | |
} | |
public void Process(IDialog dialog, string interaction) | |
{ | |
dialog.Responses = new string[] { "Sorry, we couldn't find a questionnaire under that name." }; | |
dialog.States.Push(new Null(DialogStateBuilder)); | |
dialog.IsProcessing = false; | |
} | |
} | |
public class ProcessingInput : IDialogState | |
{ | |
private readonly DialogStateBuilder DialogStateBuilder; | |
public ProcessingInput(DialogStateBuilder dialogStateBuilder) | |
{ | |
DialogStateBuilder = dialogStateBuilder; | |
} | |
public void Process(IDialog dialog, string interaction) | |
{ | |
var instruction = DialogStateBuilder.Parse(interaction); | |
if (instruction == Instruction.Reset) { | |
dialog.States.Push(new ResetRequested(DialogStateBuilder)); | |
dialog.IsProcessing = true; | |
return; | |
}; | |
var questionnaireState = (DialogQuestionnaireState)dialog.Inputs.Peek(); | |
try | |
{ | |
questionnaireState.SetResponse(interaction); | |
} | |
catch (InvalidOptionException e) | |
{ | |
var responses = new List<dynamic> { | |
"Sorry, that wasn't one of the options. Please try again", | |
}; | |
responses.AddRange(DialogStateBuilder.FormatResponse(questionnaireState.Current)); | |
dialog.Responses = responses; | |
dialog.IsProcessing = false; | |
} | |
catch (InvalidResponseException e) | |
{ | |
var responses = new List<dynamic> { | |
"That question is marked as required and needs a non-empty response.", | |
}; | |
responses.AddRange(DialogStateBuilder.FormatResponse(questionnaireState.Current)); | |
dialog.Responses = responses; | |
dialog.IsProcessing = false; | |
} | |
dialog.States.Push(new InputRequired(DialogStateBuilder)); | |
dialog.IsProcessing = true; | |
} | |
} | |
public class InputRequired : IDialogState | |
{ | |
private readonly DialogStateBuilder DialogStateBuilder; | |
public InputRequired(DialogStateBuilder dialogStateBuilder) | |
{ | |
DialogStateBuilder = dialogStateBuilder; | |
} | |
public void Process(IDialog dialog, string interaction) | |
{ | |
var questionnaireState = (DialogQuestionnaireState)dialog.Inputs.Peek(); | |
if (!questionnaireState.HasNext()) | |
{ | |
dialog.States.Push(new CreateComplete(DialogStateBuilder)); | |
dialog.IsProcessing = true; | |
return; | |
} | |
foreach (var response in DialogStateBuilder.FormatResponse(questionnaireState.Current)) | |
{ | |
dialog.Responses.Add(response); | |
} | |
dialog.States.Pop(); | |
dialog.States.Push(new ProcessingInput(DialogStateBuilder)); | |
dialog.IsProcessing = false; | |
} | |
} | |
public class CreateComplete : IDialogState | |
{ | |
private readonly DialogStateBuilder DialogStateBuilder; | |
public CreateComplete(DialogStateBuilder dialogStateBuilder) | |
{ | |
DialogStateBuilder = dialogStateBuilder; | |
} | |
public void Process(IDialog dialog, string interaction) | |
{ | |
var dialogResponses = new List<dynamic>() { "We're done!" }; | |
var questionnaireState = (DialogQuestionnaireState)dialog.Inputs.Pop(); | |
var responses = DialogStateBuilder.WebQuestionnaireService.Apply(questionnaireState.Build(), | |
DialogStateBuilder.ApplicationUserIdentity); | |
foreach(var resp in responses) | |
{ | |
if (resp is string) | |
dialogResponses.Add(resp); | |
else { | |
// todo - we should use strong types for these responses with an associated formatter | |
if (resp.recipients != null) | |
{ | |
if (resp.recipients.email != null && (resp.recipients.email as IEnumerable<string>).Count() > 0) | |
{ | |
dialogResponses.Add("Your document has been e-mailed to the following recipients:"); | |
foreach(var address in resp.recipients.email) | |
dialogResponses.Add(address); | |
if (resp.recipients.slack != null && (resp.recipients.slack as IEnumerable<string>).Count() > 0) | |
{ | |
dialogResponses.Add("...and posted to the following Slack channels:"); | |
foreach (var address in resp.recipients.slack) | |
dialogResponses.Add(address); | |
} | |
} else | |
{ | |
if (resp.recipients.slack != null && (resp.recipients.slack as IEnumerable<string>).Count() > 0) | |
{ | |
dialogResponses.Add("Your document has been posted to the following Slack channels:"); | |
foreach (var address in resp.recipients.slack) | |
dialogResponses.Add(address); | |
} | |
} | |
if(resp.downloadUrl != null) | |
{ | |
dialogResponses.Add("You can download your document by clicking on the following url:"); | |
dialogResponses.Add(resp.downloadUrl); | |
} | |
} else | |
{ | |
throw new NotSupportedException(); | |
} | |
} | |
} | |
dialog.Responses = dialogResponses; | |
dialog.States.Push(new Null(DialogStateBuilder)); | |
dialog.IsProcessing = false; | |
} | |
} | |
public class Unknown : IDialogState | |
{ | |
private readonly DialogStateBuilder DialogStateBuilder; | |
public Unknown(DialogStateBuilder dialogStateBuilder) | |
{ | |
DialogStateBuilder = dialogStateBuilder; | |
} | |
public void Process(IDialog dialog, string interaction) | |
{ | |
dialog.States = new Stack<IDialogState>(); | |
dialog.States.Push(new Null(DialogStateBuilder)); | |
dialog.Responses = new string[] { "Sorry, I didn't understand that. " }; | |
dialog.IsProcessing = false; | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment