Skip to content

Commit 8721af7

Browse files
committed
* Added Prompts.attachment() method.
* Updated Message.randomPrompt() to take a string or an array. * Updated Session to clone() raw IMessage entries before sending (fixes a serialization bug) * Fixed issue where configured BotConnectorBot endpoint wasn't getting used in production. * Tweaked the way built-in dialogs get registered. * Added support for showing Prompts.confirm() using buttons. * Improved the way re-prompting works. * Created type specific default re-prompts.
1 parent f91fec0 commit 8721af7

File tree

10 files changed

+167
-226
lines changed

10 files changed

+167
-226
lines changed

Node/src/Channel.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,20 @@
3333

3434
import ses = require('./Session');
3535

36-
export function maxButtons(session: ses.Session): number {
37-
var account = session.message.from || session.message.to;
38-
switch (account.channelId.toLowerCase()) {
36+
export function preferButtons(session: ses.Session, choiceCnt: number, rePrompt: boolean): boolean {
37+
switch (getChannelId(session)) {
3938
case 'facebook':
40-
return 3;
39+
return (choiceCnt <= 3);
4140
case 'telegram':
41+
return !rePrompt;
4242
case 'kik':
43-
return 100;
43+
return true;
4444
default:
45-
return 0;
45+
return false;
4646
}
47-
}
47+
}
48+
49+
export function getChannelId(session: ses.Session): string {
50+
var account = session.message.from || session.message.to;
51+
return account.channelId.toLowerCase();
52+
}

Node/src/Message.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,13 @@ export class Message implements IMessage {
7878
return this;
7979
}
8080

81-
static randomPrompt(prompts: string[]): string {
82-
var i = Math.floor(Math.random() * prompts.length);
83-
return prompts[i];
81+
static randomPrompt(prompts: string|string[]): string {
82+
if (Array.isArray(prompts)) {
83+
var i = Math.floor(Math.random() * prompts.length);
84+
return prompts[i];
85+
} else {
86+
return prompts;
87+
}
8488
}
8589

8690
static composePrompt(ses: session.Session, prompts: string[][], args?: any[]): string {

Node/src/Session.ts

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import dialog = require('./dialogs/Dialog');
3636
import consts = require('./consts');
3737
import sprintf = require('sprintf-js');
3838
import events = require('events');
39+
import utils = require('./utils');
3940

4041
export interface ISessionOptions {
4142
dialogs: collection.DialogCollection;
@@ -114,10 +115,7 @@ export class Session extends events.EventEmitter implements ISession {
114115
return sprintf.sprintf(tmpl, count);
115116
}
116117

117-
public send(): ISession;
118-
public send(msg: string, ...args: any[]): ISession;
119-
public send(msg: IMessage): ISession;
120-
public send(msg?: any, ...args: any[]): ISession {
118+
public send(msg?: string|IMessage, ...args: any[]): ISession {
121119
// Update dialog state
122120
// - Deals with a situation where the user assigns a whole new object to dialogState.
123121
var ss = this.sessionState;
@@ -126,7 +124,7 @@ export class Session extends events.EventEmitter implements ISession {
126124
}
127125

128126
// Compose message
129-
var message: IMessage = typeof msg == 'string' ? this.createMessage(msg, args) : msg;
127+
var message: IMessage = typeof msg == 'string' ? this.createMessage(<string>msg, args) : msg;
130128
this.delayedEmit('send', message);
131129
return this;
132130
}
@@ -173,10 +171,7 @@ export class Session extends events.EventEmitter implements ISession {
173171
return this;
174172
}
175173

176-
public endDialog(msg: string, ...args: any[]): ISession;
177-
public endDialog(msg: IMessage): ISession;
178-
public endDialog(result?: dialog.IDialogResult<any>): ISession;
179-
public endDialog(result?: any, ...args: any[]): ISession {
174+
public endDialog(result?: string|IMessage|dialog.IDialogResult<any>, ...args: any[]): ISession {
180175
// Validate callstack
181176
// - Protect against too many calls to endDialog()
182177
var ss = this.sessionState;
@@ -191,10 +186,10 @@ export class Session extends events.EventEmitter implements ISession {
191186
if (result) {
192187
if (typeof result === 'string') {
193188
m = this.createMessage(result, args);
194-
} else if (result.hasOwnProperty('text') || result.hasOwnProperty('channelData')) {
189+
} else if (result.hasOwnProperty('text') || result.hasOwnProperty('attachments') || result.hasOwnProperty('channelData')) {
195190
m = result;
196191
} else {
197-
r = result;
192+
r = <any>result;
198193
}
199194
}
200195
if (!r.hasOwnProperty('resumed')) {
@@ -300,7 +295,7 @@ export class Session extends events.EventEmitter implements ISession {
300295
setTimeout(() => {
301296
var entry = this.sendQueue.shift();
302297
this.lastSendTime = now = new Date().getTime();
303-
this.emit(entry.event, entry.msg);
298+
this.emit(entry.event, utils.clone(entry.msg));
304299
if (this.sendQueue.length > 0) {
305300
delaySend();
306301
}
@@ -311,7 +306,7 @@ export class Session extends events.EventEmitter implements ISession {
311306
this.msgSent = true;
312307
if ((now - this.lastSendTime) >= this.options.minSendDelay) {
313308
this.lastSendTime = now;
314-
this.emit(event, message);
309+
this.emit(event, utils.clone(message));
315310
} else {
316311
this.sendQueue.push({ event: event, msg: message });
317312
delaySend();

Node/src/botbuilder.d.ts

Lines changed: 16 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -305,8 +305,12 @@ export interface IDialogResult<T> {
305305
export interface IPromptOptions {
306306
/**
307307
* Optional retry prompt to send if the users response isn't understood. Default is to just
308-
* reprompt with the configured [defaultRetryPrompt](http://docs.botframework.com/sdkreference/nodejs/interfaces/_botbuilder_d_.ipromptsoptions.html#defaultretryprompt) plus the original prompt. Note that if the original
309-
* prompt is an _IMessage_ the default behaviour is to simply re-send the original prompt.
308+
* reprompt with the configured [defaultRetryPrompt](http://docs.botframework.com/sdkreference/nodejs/interfaces/_botbuilder_d_.ipromptsoptions.html#defaultretryprompt)
309+
* plus the original prompt.
310+
*
311+
* Note that if the original prompt is an _IMessage_ the retry prompt will be sent as a seperate
312+
* message followed by the original message. If the retryPrompt is also an _IMessage_ it will
313+
* instead be sent in place of the original message.
310314
* * _{string}_ - Initial message to send the user.
311315
* * _{string[]}_ - Array of possible messages to send user. One will be chosen at random.
312316
* * _{IMessage}_ - Initial message to send the user. Message can contain attachments.
@@ -367,6 +371,9 @@ export interface IPromptChoiceResult extends IPromptResult<IFindMatchResult> { }
367371
/** Strongly typed Time Prompt Result. */
368372
export interface IPromptTimeResult extends IPromptResult<IEntity> { }
369373

374+
/** Strongly typed Attachment Prompt Result. */
375+
export interface IPromptAttachmentResult extends IPromptResult<IAttachment[]> { }
376+
370377
/** Plugin for recognizing prompt responses recieved by a user. */
371378
export interface IPromptRecognizer {
372379
/**
@@ -412,40 +419,6 @@ export interface IPromptRecognizerArgs {
412419
export interface IPromptsOptions {
413420
/** Replaces the default recognizer (SimplePromptRecognizer) used to recognize prompt replies. */
414421
recognizer?: IPromptRecognizer
415-
416-
/** The default retry prompt to use. The default value is "I didn't understand." */
417-
defaultRetryPrompt?: string;
418-
}
419-
420-
/** Context information passed to field related handlers. */
421-
export interface IFieldConext {
422-
userData: any;
423-
form: any;
424-
field: string;
425-
}
426-
427-
/**
428-
* Handler that gets called anytime a field is about to invoke a prompt.
429-
*/
430-
export interface IFieldPromptHandler {
431-
/**
432-
* @param context Contextual information related to the current field.
433-
* @param next Function to call to continue execution of the prompt.
434-
* Passing _true_ for __skip__ will cause the current field to be skipped.
435-
*/
436-
(context: IFieldConext, next: (skip: boolean) => void): void;
437-
}
438-
439-
/** Options passed to form fields. */
440-
export interface IFieldOptions extends IPromptOptions {
441-
/**
442-
* Called anytime a given field is about to invoke the prompt. This function lets the
443-
* developer progromatically determine if a prompt should be skipped or not.
444-
*
445-
* The handler can also manipulate the forms current values. For instance a fields value
446-
* could be pulled from context.userData if not already specified on the form.
447-
*/
448-
onPrompt?: IFieldPromptHandler;
449422
}
450423

451424
/** A recognized intent. */
@@ -1149,9 +1122,9 @@ export class Message implements IMessage {
11491122

11501123
/**
11511124
* Selects a prompt at random.
1152-
* @param prompts Array of prompts to choose from.
1125+
* @param prompts Array of prompts to choose from. When prompts is type _string_ the prompt will simply be returned unmodified.
11531126
*/
1154-
static randomPrompt(prompts: string[]): string;
1127+
static randomPrompt(prompts: string|string[]): string;
11551128

11561129
/**
11571130
* Combines an array of prompts into a single localized prompt and then optionally fills the
@@ -1389,84 +1362,17 @@ export class Prompts extends Dialog {
13891362
* @param options Optional parameters to control the behaviour of the prompt.
13901363
*/
13911364
static time(session: Session, prompt: string|string[]|IMessage, options?: IPromptOptions): void;
1392-
}
1393-
1394-
/**
1395-
* Fields that can be added as setps to a waterfall to create a form.
1396-
*/
1397-
export class Fields {
1398-
/**
1399-
* Captures from the user a raw string of text and saves it to a field on a form.
1400-
* @param field Name of the field to save the users response to.
1401-
* @param prompt
1402-
* * __prompt:__ _{string}_ - Initial message to send the user.
1403-
* * __prompt:__ _{string[]}_ - Array of possible messages to send user. One will be chosen at random.
1404-
* @param options Optional parameters to control the behaviour of the field.
1405-
*/
1406-
static text(field: string, prompt: string|string[], options?: IFieldOptions): IDialogWaterfallStep;
1407-
1408-
/**
1409-
* Prompts the user to enter a number and saves it to a field on a form.
1410-
* @param field Name of the field to save the users response to.
1411-
* @param prompt
1412-
* * __prompt:__ _{string}_ - Initial message to send the user.
1413-
* * __prompt:__ _{string[]}_ - Array of possible messages to send user. One will be chosen at random.
1414-
* @param options Optional parameters to control the behaviour of the field.
1415-
*/
1416-
static number(field: string, prompt: string|string[], options?: IFieldOptions): IDialogWaterfallStep;
1417-
1418-
/**
1419-
* Prompts the user to enter a boolean yes/no response and saves their answer to a field on a form.
1420-
* @param field Name of the field to save the users response to.
1421-
* @param prompt
1422-
* * __prompt:__ _{string}_ - Initial message to send the user.
1423-
* * __prompt:__ _{string[]}_ - Array of possible messages to send user. One will be chosen at random.
1424-
* @param options Optional parameters to control the behaviour of the field.
1425-
*/
1426-
static confirm(field: string, prompt: string|string[], options?: IFieldOptions): IDialogWaterfallStep;
1427-
1428-
/**
1429-
* Prompts the user to choose from a list of options and saves their selection to a field on a form.
1430-
* @param field Name of the field to save the users response to.
1431-
* @param prompt
1432-
* * __prompt:__ _{string}_ - Initial message to send the user.
1433-
* * __prompt:__ _{string[]}_ - Array of possible messages to send user. One will be chosen at random.
1434-
* @param choices
1435-
* * __choices:__ _{string}_ - List of choices as a pipe ('|') delimted string.
1436-
* * __choices:__ _{Object}_ - List of choices expressed as an Object map. The objects field names will be used to build the list of values.
1437-
* * __choices:__ _{string[]}_ - List of choices as an array of strings.
1438-
* @param options Optional parameters to control the behaviour of the field.
1439-
*/
1440-
static choice(field: string, prompt: string|string[], choices: string|Object|string[], options?: IFieldOptions): IDialogWaterfallStep;
14411365

14421366
/**
1443-
* Prompts the user to enter a time saves it to a field on a form as a timestamp.
1444-
* @param field Name of the field to save the users response to.
1367+
* Prompts the user to upload a file attachment.
1368+
* @param session Session object for the current conversation.
14451369
* @param prompt
14461370
* * __prompt:__ _{string}_ - Initial message to send the user.
14471371
* * __prompt:__ _{string[]}_ - Array of possible messages to send user. One will be chosen at random.
1448-
* @param options Optional parameters to control the behaviour of the field.
1449-
*/
1450-
static time(field: string, prompt: string|string[], options?: IPromptOptions): IDialogWaterfallStep;
1451-
1452-
/**
1453-
* Finalizes the form by saving the response from the last prompt and then passes the completed
1454-
* form to the next step of the waterfall for processing.
1455-
*/
1456-
static endForm(): IDialogWaterfallStep;
1457-
1458-
/**
1459-
* Finalizes the form by saving the response from the last prompt and then returns the completed
1460-
* form to the parent dialog by calling [endDialog()](http://docs.botframework.com/sdkreference/nodejs/classes/_botbuilder_d_.session.html#enddialog).
1461-
*/
1462-
static returnForm(): IDialogWaterfallStep;
1463-
1464-
/**
1465-
* Handler for IFieldOptions.onPrompt that will copy a default value from Session.userData if
1466-
* a field is empty. The default value must be in a property with the same name as the field.
1467-
* If successfully copied the prompt will be skipped.
1372+
* * __prompt:__ _{IMessage}_ - Initial message to send the user. Message can contain attachments.
1373+
* @param options Optional parameters to control the behaviour of the prompt.
14681374
*/
1469-
static onPromptUseDefault(): IFieldPromptHandler
1375+
static attachment(session: Session, prompt: string|string[]|IMessage, options?: IPromptOptions): void;
14701376
}
14711377

14721378
/**

Node/src/botbuilder.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ import dialog = require('./dialogs/Dialog');
3939
import actions = require('./dialogs/DialogAction');
4040
import collection = require('./dialogs/DialogCollection');
4141
import prompts = require('./dialogs/Prompts');
42-
import fields = require('./dialogs/Fields');
4342
import intent = require('./dialogs/IntentDialog');
4443
import luis = require('./dialogs/LuisDialog');
4544
import command = require('./dialogs/CommandDialog');
@@ -62,7 +61,6 @@ exports.DialogCollection = collection.DialogCollection;
6261
exports.PromptType = prompts.PromptType;
6362
exports.ListStyle = prompts.ListStyle;
6463
exports.Prompts = prompts.Prompts;
65-
exports.Fields = fields.Fields;
6664
exports.SimplePromptRecognizer = prompts.SimplePromptRecognizer;
6765
exports.IntentDialog = intent.IntentDialog;
6866
exports.IntentGroup = intent.IntentGroup;

Node/src/bots/BotConnectorBot.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ export class BotConnectorBot extends collection.DialogCollection {
295295
if (ses.message.to.channelId == 'emulator') {
296296
endpoint = this.options.endpoint || 'http://localhost:9000';
297297
} else {
298-
endpoint = 'https://api.botframework.com';
298+
endpoint = this.options.endpoint || 'https://api.botframework.com';
299299
}
300300

301301
// Send message

Node/src/dialogs/DialogCollection.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,20 +35,21 @@ import dialog = require('./Dialog');
3535
import actions = require('./DialogAction');
3636
import simpleDialog = require('./SimpleDialog');
3737
import events = require('events');
38-
import prompts = require('./Prompts');
3938
import consts = require('../consts');
4039

41-
interface IDialogMap {
40+
export interface IDialogMap {
4241
[id: string]: dialog.IDialog;
4342
}
4443

44+
export var systemDialogs: IDialogMap = {};
45+
4546
export class DialogCollection extends events.EventEmitter {
4647
private middleware: { (session: ISession, next: Function): void; }[] = [];
4748
private dialogs: IDialogMap = {};
4849

4950
constructor() {
5051
super();
51-
this.add(consts.DialogId.Prompts, new prompts.Prompts())
52+
this.add(systemDialogs);
5253
}
5354

5455
public add(dialogs: { [id: string]: dialog.IDialog; }): DialogCollection;

Node/src/dialogs/EntityRecognizer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ export interface IFindMatchResult {
6262

6363
export class EntityRecognizer {
6464
static dateExp = /^\d{4}-\d{2}-\d{2}/i;
65-
static yesExp = /^(1|y|yes|yep|sure|ok|true)/i;
66-
static noExp = /^(0|n|no|nope|not|false)/i;
65+
static yesExp = /^(y|yes|yep|sure|ok|true)/i;
66+
static noExp = /^(n|no|nope|not|false)/i;
6767
static numberExp = /[+-]?(?:\d+\.?\d*|\d*\.?\d+)/;
6868

6969
static findEntity(entities: IEntity[], type: string): IEntity {

0 commit comments

Comments
 (0)