/
userProfileDialog.js
203 lines (166 loc) · 7.91 KB
/
userProfileDialog.js
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
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
const { MessageFactory } = require('botbuilder');
const {
AttachmentPrompt,
ChoiceFactory,
ChoicePrompt,
ComponentDialog,
ConfirmPrompt,
DialogSet,
DialogTurnStatus,
NumberPrompt,
TextPrompt,
WaterfallDialog
} = require('botbuilder-dialogs');
const { Channels } = require('botbuilder-core');
const { UserProfile } = require('../userProfile');
const ATTACHMENT_PROMPT = 'ATTACHMENT_PROMPT';
const CHOICE_PROMPT = 'CHOICE_PROMPT';
const CONFIRM_PROMPT = 'CONFIRM_PROMPT';
const NAME_PROMPT = 'NAME_PROMPT';
const NUMBER_PROMPT = 'NUMBER_PROMPT';
const USER_PROFILE = 'USER_PROFILE';
const WATERFALL_DIALOG = 'WATERFALL_DIALOG';
class UserProfileDialog extends ComponentDialog {
constructor(userState) {
super('userProfileDialog');
this.userProfile = userState.createProperty(USER_PROFILE);
this.addDialog(new TextPrompt(NAME_PROMPT));
this.addDialog(new ChoicePrompt(CHOICE_PROMPT));
this.addDialog(new ConfirmPrompt(CONFIRM_PROMPT));
this.addDialog(new NumberPrompt(NUMBER_PROMPT, this.agePromptValidator));
this.addDialog(new AttachmentPrompt(ATTACHMENT_PROMPT, this.picturePromptValidator));
this.addDialog(new WaterfallDialog(WATERFALL_DIALOG, [
this.transportStep.bind(this),
this.nameStep.bind(this),
this.nameConfirmStep.bind(this),
this.ageStep.bind(this),
this.pictureStep.bind(this),
this.summaryStep.bind(this),
this.confirmStep.bind(this)
]));
this.initialDialogId = WATERFALL_DIALOG;
}
/**
* The run method handles the incoming activity (in the form of a TurnContext) and passes it through the dialog system.
* If no dialog is active, it will start the default dialog.
* @param {*} turnContext
* @param {*} accessor
*/
async run(turnContext, accessor) {
const dialogSet = new DialogSet(accessor);
dialogSet.add(this);
const dialogContext = await dialogSet.createContext(turnContext);
const results = await dialogContext.continueDialog();
if (results.status === DialogTurnStatus.empty) {
await dialogContext.beginDialog(this.id);
}
}
async transportStep(step) {
// WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
// Running a prompt here means the next WaterfallStep will be run when the user's response is received.
return await step.prompt(CHOICE_PROMPT, {
prompt: 'Please enter your mode of transport.',
choices: ChoiceFactory.toChoices(['Car', 'Bus', 'Bicycle'])
});
}
async nameStep(step) {
step.values.transport = step.result.value;
return await step.prompt(NAME_PROMPT, 'Please enter your name.');
}
async nameConfirmStep(step) {
step.values.name = step.result;
// We can send messages to the user at any point in the WaterfallStep.
await step.context.sendActivity(`Thanks ${ step.result }.`);
// WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
return await step.prompt(CONFIRM_PROMPT, 'Do you want to give your age?', ['yes', 'no']);
}
async ageStep(step) {
if (step.result) {
// User said "yes" so we will be prompting for the age.
// WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
const promptOptions = { prompt: 'Please enter your age.', retryPrompt: 'The value entered must be greater than 0 and less than 150.' };
return await step.prompt(NUMBER_PROMPT, promptOptions);
} else {
// User said "no" so we will skip the next step. Give -1 as the age.
return await step.next(-1);
}
}
async pictureStep(step) {
step.values.age = step.result;
const msg = step.values.age === -1 ? 'No age given.' : `I have your age as ${ step.values.age }.`;
// We can send messages to the user at any point in the WaterfallStep.
await step.context.sendActivity(msg);
if (step.context.activity.channelId === Channels.msteams) {
// This attachment prompt example is not designed to work for Teams attachments, so skip it in this case
await step.context.sendActivity('Skipping attachment prompt in Teams channel...');
return await step.next(undefined);
} else {
// WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
var promptOptions = {
prompt: 'Please attach a profile picture (or type any message to skip).',
retryPrompt: 'The attachment must be a jpeg/png image file.'
};
return await step.prompt(ATTACHMENT_PROMPT, promptOptions);
}
}
async confirmStep(step) {
let msg = 'Thanks.';
if (step.result) {
msg += ' Your profile saved successfully.';
} else {
msg += ' Your profile will not be kept.';
}
await step.context.sendActivity(msg);
// WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
return await step.endDialog();
}
async summaryStep(step) {
step.values.picture = step.result && step.result[0];
// Get the current profile object from user state.
const userProfile = await this.userProfile.get(step.context, new UserProfile());
userProfile.transport = step.values.transport;
userProfile.name = step.values.name;
userProfile.age = step.values.age;
userProfile.picture = step.values.picture;
let msg = `I have your mode of transport as ${ userProfile.transport } and your name as ${ userProfile.name }`;
if (userProfile.age !== -1) {
msg += ` and your age as ${ userProfile.age }`;
}
msg += '.';
await step.context.sendActivity(msg);
if (userProfile.picture) {
try {
await step.context.sendActivity(MessageFactory.attachment(userProfile.picture, 'This is your profile picture.'));
} catch {
await step.context.sendActivity('A profile picture was saved but could not be displayed here.');
}
}
// WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is the end.
return await step.prompt(CONFIRM_PROMPT, { prompt: 'Is this okay?' });
}
async agePromptValidator(promptContext) {
// This condition is our validation rule. You can also change the value at this point.
return promptContext.recognized.succeeded && promptContext.recognized.value > 0 && promptContext.recognized.value < 150;
}
async picturePromptValidator(promptContext) {
if (promptContext.recognized.succeeded) {
var attachments = promptContext.recognized.value;
var validImages = [];
attachments.forEach(attachment => {
if (attachment.contentType === 'image/jpeg' || attachment.contentType === 'image/png') {
validImages.push(attachment);
}
});
promptContext.recognized.value = validImages;
// If none of the attachments are valid images, the retry prompt should be sent.
return !!validImages.length;
} else {
await promptContext.context.sendActivity('No attachments received. Proceeding without a profile picture...');
// We can return true from a validator function even if Recognized.Succeeded is false.
return true;
}
}
}
module.exports.UserProfileDialog = UserProfileDialog;