forked from microsoft/BotBuilder-Samples
-
Notifications
You must be signed in to change notification settings - Fork 0
/
CafeBot.cs
221 lines (189 loc) · 10.5 KB
/
CafeBot.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Schema;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
namespace Microsoft.BotBuilderSamples
{
/// <summary>
/// For each interaction from the user, an instance of this class is created and
/// the OnTurnAsync method is called.
/// This is a transient lifetime service. Transient lifetime services are created
/// each time they"re requested. For each <see cref="Activity"/> received, a new instance of this
/// class is created. Objects that are expensive to construct, or have a lifetime
/// beyond the single turn, should be carefully managed.
/// </summary>
/// <seealso cref="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.1"/>
/// <seealso cref="https://docs.microsoft.com/en-us/dotnet/api/microsoft.bot.ibot?view=botbuilder-dotnet-preview"/>
public class CafeBot : IBot
{
// Supported LUIS Intents.
public const string GreetingIntent = "Greeting";
public const string HelpIntent = "Help";
// Conversation State properties.
public const string OnTurnPropertyName = "onTurnStateProperty";
public const string DialogStateProperty = "dialogStateProperty";
// The name of the bot you deployed.
public static readonly string MsBotName = "cafe66";
/// <summary>
/// Key in the bot config (.bot file) for the LUIS instances.
/// In the .bot file, multiple instances of LUIS can be configured.
/// </summary>
public static readonly string LuisConfiguration = MsBotName + "_" + "cafeDispatchModel";
// Greeting Dialog ID.
public static readonly string GreetingDialogId = "greetingDialog";
/// <summary>
/// Services configured from the .bot file.
/// </summary>
private readonly BotServices _services;
private readonly IStatePropertyAccessor<OnTurnProperty> _onTurnAccessor;
private readonly IStatePropertyAccessor<DialogState> _dialogAccessor;
/// <summary>
/// Top level dialog(s).
/// </summary>
private readonly DialogSet _dialogs;
private readonly ILogger _logger;
/// <summary>
/// Initializes a new instance of the <see cref="CafeBot"/> class.
/// </summary>
/// <param name="userState">The <see cref="UserState"/> for properties at user scope.</param>
/// <param name="conversationState">The <see cref="ConversationState"/> for properties at conversation scope.</param>
/// <param name="services">The <see cref="BotServices"/> which holds clients for external services.</param>
/// <param name="loggerFactory">A <see cref="ILoggerFactory"/> that hooked to the Azure App Service provider.</param>
/// <seealso cref="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-2.1#windows-eventlog-provider"/>
/// <seealso cref="BotConfiguration"/>
public CafeBot(UserState userState, ConversationState conversationState, BotServices services, ILoggerFactory loggerFactory)
{
if (loggerFactory == null)
{
throw new System.ArgumentNullException(nameof(loggerFactory));
}
ConversationState = conversationState ?? throw new System.ArgumentNullException(nameof(conversationState));
UserState = userState ?? throw new System.ArgumentNullException(nameof(userState));
_services = services ?? throw new System.ArgumentNullException(nameof(services));
// Create state property accessors.
_onTurnAccessor = conversationState.CreateProperty<OnTurnProperty>(OnTurnPropertyName);
_dialogAccessor = conversationState.CreateProperty<DialogState>(DialogStateProperty);
// Create logger.
_logger = loggerFactory.CreateLogger<CafeBot>();
_logger.LogTrace("CafeBot turn start.");
if (!_services.LuisServices.ContainsKey(LuisConfiguration))
{
throw new System.ArgumentException($"Invalid configuration. Please check your '.bot' file for a LUIS service named {LuisConfiguration}.");
}
// Create top-level dialog.
_dialogs = new DialogSet(_dialogAccessor);
_dialogs.Add(new MainDispatcher(services, _onTurnAccessor, userState, conversationState, loggerFactory));
}
/// <summary>
/// Gets the <see cref="ConversationState"/> object for the conversation.
/// </summary>
/// <value>The <see cref="ConversationState"/> object.</value>
public ConversationState ConversationState { get; }
/// <summary>
/// Gets the <see cref="UserState"/> object for the conversation.
/// </summary>
/// <value>The <see cref="UserState"/> object.</value>
public UserState UserState { get; }
/// <summary>
/// Every conversation turn for our Basic Bot will call this method.
/// </summary>
/// <param name="context">A <see cref="ITurnContext"/> containing all the data needed
/// for processing this conversation turn. </param>
/// <param name="cancellationToken">(Optional) A <see cref="CancellationToken"/> that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A <see cref="Task"/> that represents the work queued to execute.</returns>
public async Task OnTurnAsync(ITurnContext context, CancellationToken cancellationToken = default(CancellationToken))
{
// See https://aka.ms/about-bot-activity-message to learn more about message and other activity types.
switch (context.Activity.Type)
{
case ActivityTypes.Message:
// Process on turn input (card or NLP) and gather new properties.
// OnTurnProperty object has processed information from the input message activity.
var onTurnProperties = await DetectIntentAndEntitiesAsync(context);
if (onTurnProperties == null)
{
break;
}
// Set the state with gathered properties (intent/ entities) through the onTurnAccessor.
await _onTurnAccessor.SetAsync(context, onTurnProperties);
// Create dialog context.
var dc = await _dialogs.CreateContextAsync(context);
// Continue outstanding dialogs.
await dc.ContinueDialogAsync();
// Begin main dialog if no outstanding dialogs/ no one responded.
if (!dc.Context.Responded)
{
await dc.BeginDialogAsync(nameof(MainDispatcher));
}
break;
case ActivityTypes.ConversationUpdate:
// Welcome user.
await WelcomeUserAsync(context);
break;
default:
// Handle other activity types as needed.
await context.SendActivityAsync($"{context.Activity.Type} event detected");
break;
}
await ConversationState.SaveChangesAsync(context);
await UserState.SaveChangesAsync(context);
}
private async Task<OnTurnProperty> DetectIntentAndEntitiesAsync(ITurnContext turnContext)
{
// Handle card input (if any), update state and return.
if (turnContext.Activity.Value != null)
{
var response = JsonConvert.DeserializeObject<Dictionary<string, string>>(turnContext.Activity.Value as string);
return OnTurnProperty.FromCardInput(response);
}
// Acknowledge attachments from user.
if (turnContext.Activity.Attachments != null && turnContext.Activity.Attachments.Count > 0)
{
await turnContext.SendActivityAsync("Thanks for sending me that attachment. I'm still learning to process attachments.");
return null;
}
// Nothing to do for this turn if there is no text specified.
if (string.IsNullOrWhiteSpace(turnContext.Activity.Text) || string.IsNullOrWhiteSpace(turnContext.Activity.Text.Trim()))
{
return null;
}
// Call to LUIS recognizer to get intent + entities.
var luisResults = await _services.LuisServices[LuisConfiguration].RecognizeAsync(turnContext, default(CancellationToken));
// Return new instance of on turn property from LUIS results.
// Leverages static fromLUISResults method.
return OnTurnProperty.FromLuisResults(luisResults);
}
private async Task WelcomeUserAsync(ITurnContext turnContext)
{
// Check to see if any new members were added to the conversation.
if (turnContext.Activity.MembersAdded.Count > 0)
{
// Iterate over all new members added to the conversation.
foreach (var member in turnContext.Activity.MembersAdded)
{
// Greet anyone that was not the target (recipient) of this message
// the 'bot' is the recipient for events from the channel,
// turnContext.activity.membersAdded == turnContext.activity.recipient.Id indicates the
// bot was added to the conversation.
if (member.Id != turnContext.Activity.Recipient.Id)
{
// Welcome user.
await turnContext.SendActivityAsync("Hello, I am the Contoso Cafe Bot!");
await turnContext.SendActivityAsync("I can help book a table and more..");
var activity = turnContext.Activity.CreateReply();
activity.Attachments = new List<Attachment> { Helpers.CreateAdaptiveCardAttachment(@".\Dialogs\Welcome\Resources\welcomeCard.json"), };
// Send welcome card.
await turnContext.SendActivityAsync(activity);
}
}
}
}
}
}