botbuilder-dialogs
Advanced tools
Comparing version 4.0.0-m1.11 to 4.0.0-m2.1
@@ -8,4 +8,4 @@ /** | ||
*/ | ||
import { Promiseable } from 'botbuilder'; | ||
import { DialogSet } from './dialogSet'; | ||
import { BotContext, Promiseable } from 'botbuilder'; | ||
import { DialogContext } from './dialogContext'; | ||
/** | ||
@@ -16,10 +16,9 @@ * Interface of Dialog objects that can be added to a `DialogSet`. The dialog should generally | ||
*/ | ||
export interface Dialog { | ||
export interface Dialog<C extends BotContext> { | ||
/** | ||
* Method called when a new dialog has been pushed onto the stack and is being activated. | ||
* @param context The dialog context for the current turn of conversation. | ||
* @param dialogs The dialogs parent set. | ||
* @param args (Optional) arguments that were passed to the dialog during `begin()` call that started the instance. | ||
* @param dc The dialog context for the current turn of conversation. | ||
* @param dialogArgs (Optional) arguments that were passed to the dialog during `begin()` call that started the instance. | ||
*/ | ||
begin(context: BotContext, dialogs: DialogSet, args?: any): Promiseable<void>; | ||
dialogBegin(dc: DialogContext<C>, dialogArgs?: any): Promiseable<any>; | ||
/** | ||
@@ -32,6 +31,5 @@ * (Optional) method called when an instance of the dialog is the "current" dialog and the | ||
* replies. | ||
* @param context The dialog context for the current turn of conversation. | ||
* @param dialogs The dialogs parent set. | ||
* @param dc The dialog context for the current turn of conversation. | ||
*/ | ||
continue?(context: BotContext, dialogs: DialogSet): Promiseable<void>; | ||
dialogContinue?(dc: DialogContext<C>): Promiseable<any>; | ||
/** | ||
@@ -44,7 +42,6 @@ * (Optional) method called when an instance of the dialog is being returned to from another | ||
* to the current dialogs parent. | ||
* @param context The dialog context for the current turn of conversation. | ||
* @param dialogs The dialogs parent set. | ||
* @param dc The dialog context for the current turn of conversation. | ||
* @param result (Optional) value returned from the dialog that was called. The type of the value returned is dependant on the dialog that was called. | ||
*/ | ||
resume?(context: BotContext, dialogs: DialogSet, result?: any): Promiseable<void>; | ||
dialogResume?(dc: DialogContext<C>, result?: any): Promiseable<any>; | ||
} | ||
@@ -54,3 +51,3 @@ /** | ||
*/ | ||
export interface DialogInstance<T extends Object> { | ||
export interface DialogInstance<T extends any = any> { | ||
/** ID of the dialog this instance is for. */ | ||
@@ -57,0 +54,0 @@ id: string; |
@@ -8,7 +8,6 @@ /** | ||
*/ | ||
import { Activity } from 'botbuilder'; | ||
import { Dialog, DialogInstance } from './dialog'; | ||
import { BotContext } from 'botbuilder'; | ||
import { Dialog } from './dialog'; | ||
import { Waterfall, WaterfallStep } from './waterfall'; | ||
import { PromptOptions } from './prompts/index'; | ||
import { Choice } from 'botbuilder-choices'; | ||
import { DialogContext } from './dialogContext'; | ||
/** | ||
@@ -47,19 +46,5 @@ * A related set of dialogs that can all call each other. | ||
*/ | ||
export declare class DialogSet { | ||
private readonly stackName; | ||
export declare class DialogSet<C extends BotContext = BotContext> { | ||
private readonly dialogs; | ||
/** | ||
* Creates an empty dialog set. The ability to name the sets dialog stack means that multiple | ||
* stacks can coexist within the same bot. Middleware can use their own private set of | ||
* dialogs without fear of colliding with the bots dialog stack. | ||
* | ||
* **Example usage:** | ||
* | ||
* ```JavaScript | ||
* const dialogs = new DialogSet('myPrivateStack'); | ||
* ``` | ||
* @param stackName (Optional) name of the field to store the dialog stack in off the bots conversation state object. This defaults to 'dialogStack'. | ||
*/ | ||
constructor(stackName?: string); | ||
/** | ||
* Adds a new dialog to the set and returns the added dialog. | ||
@@ -81,89 +66,6 @@ * | ||
*/ | ||
add<T extends Dialog>(dialogId: string, dialogOrSteps: T): T; | ||
add(dialogId: string, dialogOrSteps: WaterfallStep[]): Waterfall; | ||
add(dialogId: string, dialogOrSteps: Dialog<C>): Dialog<C>; | ||
add(dialogId: string, dialogOrSteps: WaterfallStep<C>[]): Waterfall<C>; | ||
createContext(context: C, state: object): DialogContext<C>; | ||
/** | ||
* Pushes a new dialog onto the dialog stack. | ||
* | ||
* **Example usage:** | ||
* | ||
* ```JavaScript | ||
* return dialogs.begin(context, 'greeting', user); | ||
* ``` | ||
* @param context Context object for the current turn of conversation with the user. | ||
* @param dialogId ID of the dialog to start. | ||
* @param dialogArgs (Optional) additional argument(s) to pass to the dialog being started. | ||
*/ | ||
begin(context: BotContext, dialogId: string, dialogArgs?: any): Promise<void>; | ||
/** | ||
* Helper function to simplify formatting the options for calling a prompt dialog. This helper will | ||
* construct a `PromptOptions` structure and then call [begin(context, dialogId, options)](#begin). | ||
* | ||
* **Example usage:** | ||
* | ||
* ```JavaScript | ||
* return dialogs.prompt(context, 'confirmPrompt', `Are you sure you'd like to quit?`); | ||
* ``` | ||
* @param O (Optional) type of options expected by the prompt. | ||
* @param context Context object for the current turn of conversation with the user. | ||
* @param dialogId ID of the prompt to start. | ||
* @param prompt Initial prompt to send the user. | ||
* @param choicesOrOptions (Optional) array of choices to prompt the user for or additional prompt options. | ||
*/ | ||
prompt<O extends PromptOptions = PromptOptions>(context: BotContext, dialogId: string, prompt: string | Partial<Activity>, choicesOrOptions?: O | (string | Choice)[], options?: O): Promise<void>; | ||
/** | ||
* Continues execution of the active dialog, if there is one, by passing the context object to | ||
* its `Dialog.continue()` method. You can check `context.responded` after the call completes | ||
* to determine if a dialog was run and a reply was sent to the user. | ||
* | ||
* **Example usage:** | ||
* | ||
* ```JavaScript | ||
* return dialogs.continue(context).then(() => { | ||
* if (!dialog.responded) { | ||
* return dialogs.begin(context, 'fallback'); | ||
* } | ||
* }); | ||
* ``` | ||
* @param context Context object for the current turn of conversation with the user. | ||
*/ | ||
continue(context: BotContext): Promise<void>; | ||
/** | ||
* Ends a dialog by popping it off the stack and returns an optional result to the dialogs | ||
* parent. The parent dialog is the dialog the started the on being ended via a call to | ||
* either [begin()](#begin) or [prompt()](#prompt). | ||
* | ||
* The parent dialog will have its `Dialog.resume()` method invoked with any returned | ||
* result. If the parent dialog hasn't implemented a `resume()` method then it will be | ||
* automatically ended as well and the result passed to its parent. If there are no more | ||
* parent dialogs on the stack then processing of the turn will end. | ||
* | ||
* **Example usage:** | ||
* | ||
* ```JavaScript | ||
* dialogs.add('showUptime', [ | ||
* function (context) { | ||
* const elapsed = new Date().getTime() - started; | ||
* context.reply(`I've been running for ${elapsed / 1000} seconds.`); | ||
* return dialogs.end(context, elapsed); | ||
* } | ||
* ]) | ||
* const started = new Date().getTime(); | ||
* ``` | ||
* @param context Context object for the current turn of conversation with the user. | ||
* @param result (Optional) result to pass to the parent dialogs `Dialog.resume()` method. | ||
*/ | ||
end(context: BotContext, result?: any): Promise<void>; | ||
/** | ||
* Deletes any existing dialog stack thus cancelling all dialogs on the stack. | ||
* | ||
* **Example usage:** | ||
* | ||
* ```JavaScript | ||
* return dialogs.endAll(context) | ||
* .then(() => dialogs.begin(context, 'addAlarm')); | ||
* ``` | ||
* @param context Context object for the current turn of conversation with the user. | ||
*/ | ||
endAll(context: BotContext): Promise<void>; | ||
/** | ||
* Finds a dialog that was previously added to the set using [add()](#add). | ||
@@ -179,53 +81,3 @@ * | ||
*/ | ||
find<T extends Dialog = Dialog>(dialogId: string): T | undefined; | ||
/** | ||
* Returns the dialog stack persisted for a conversation. | ||
* | ||
* **Example usage:** | ||
* | ||
* ```JavaScript | ||
* const hasActiveDialog = dialogs.getStack(context).length > 0; | ||
* ``` | ||
* @param context Context object for the current turn of conversation with the user. | ||
*/ | ||
getStack(context: BotContext): DialogInstance<any>[]; | ||
/** | ||
* Returns the active dialog instance on the top of the stack. Throws an error if the stack is | ||
* empty so use `dialogs.getStack(context).length > 0` to protect calls where the stack could | ||
* be empty. | ||
* | ||
* **Example usage:** | ||
* | ||
* ```JavaScript | ||
* const dialogState = dialogs.getInstance(context).state; | ||
* ``` | ||
* @param T (Optional) type of dialog state being persisted by the instance. | ||
* @param context Context object for the current turn of conversation with the user. | ||
*/ | ||
getInstance<T extends Object = { | ||
[key: string]: any; | ||
}>(context: BotContext): DialogInstance<T>; | ||
/** | ||
* Ends the current dialog and starts a new dialog in its place. This is particularly useful | ||
* for creating loops or redirecting to another dialog. | ||
* | ||
* **Example usage:** | ||
* | ||
* ```JavaScript | ||
* dialogs.add('loop', [ | ||
* function (context, args) { | ||
* dialogs.getInstance(context).state = args; | ||
* return dialogs.begin(context, args.dialogId); | ||
* }, | ||
* function (context) { | ||
* const args = dialogs.getInstance(context).state; | ||
* return dialogs.replace(context, 'loop', args); | ||
* } | ||
* ]); | ||
* ``` | ||
* @param context Context object for the current turn of conversation with the user. | ||
* @param dialogId ID of the new dialog to start. | ||
* @param dialogArgs (Optional) additional argument(s) to pass to the new dialog. | ||
*/ | ||
replace(context: BotContext, dialogId: string, dialogArgs?: any): Promise<void>; | ||
find<T extends Dialog<C> = Dialog<C>>(dialogId: string): T | undefined; | ||
} |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const waterfall_1 = require("./waterfall"); | ||
const dialogContext_1 = require("./dialogContext"); | ||
/** | ||
@@ -38,17 +39,4 @@ * A related set of dialogs that can all call each other. | ||
class DialogSet { | ||
/** | ||
* Creates an empty dialog set. The ability to name the sets dialog stack means that multiple | ||
* stacks can coexist within the same bot. Middleware can use their own private set of | ||
* dialogs without fear of colliding with the bots dialog stack. | ||
* | ||
* **Example usage:** | ||
* | ||
* ```JavaScript | ||
* const dialogs = new DialogSet('myPrivateStack'); | ||
* ``` | ||
* @param stackName (Optional) name of the field to store the dialog stack in off the bots conversation state object. This defaults to 'dialogStack'. | ||
*/ | ||
constructor(stackName) { | ||
constructor() { | ||
this.dialogs = {}; | ||
this.stackName = stackName || 'dialogStack'; | ||
} | ||
@@ -61,182 +49,9 @@ add(dialogId, dialogOrSteps) { | ||
} | ||
/** | ||
* Pushes a new dialog onto the dialog stack. | ||
* | ||
* **Example usage:** | ||
* | ||
* ```JavaScript | ||
* return dialogs.begin(context, 'greeting', user); | ||
* ``` | ||
* @param context Context object for the current turn of conversation with the user. | ||
* @param dialogId ID of the dialog to start. | ||
* @param dialogArgs (Optional) additional argument(s) to pass to the dialog being started. | ||
*/ | ||
begin(context, dialogId, dialogArgs) { | ||
try { | ||
// Lookup dialog | ||
const dialog = this.find(dialogId); | ||
if (!dialog) { | ||
throw new Error(`DialogSet.begin(): A dialog with an id of '${dialogId}' wasn't found.`); | ||
} | ||
// Push new instance onto stack. | ||
const instance = { | ||
id: dialogId, | ||
state: {} | ||
}; | ||
this.getStack(context).push(instance); | ||
// Call dialogs begin() method. | ||
return Promise.resolve(dialog.begin(context, this, dialogArgs)); | ||
createContext(context, state) { | ||
if (!Array.isArray(state['dialogStack'])) { | ||
state['dialogStack'] = []; | ||
} | ||
catch (err) { | ||
return Promise.reject(err); | ||
} | ||
return new dialogContext_1.DialogContext(this, context, state['dialogStack']); | ||
} | ||
/** | ||
* Helper function to simplify formatting the options for calling a prompt dialog. This helper will | ||
* construct a `PromptOptions` structure and then call [begin(context, dialogId, options)](#begin). | ||
* | ||
* **Example usage:** | ||
* | ||
* ```JavaScript | ||
* return dialogs.prompt(context, 'confirmPrompt', `Are you sure you'd like to quit?`); | ||
* ``` | ||
* @param O (Optional) type of options expected by the prompt. | ||
* @param context Context object for the current turn of conversation with the user. | ||
* @param dialogId ID of the prompt to start. | ||
* @param prompt Initial prompt to send the user. | ||
* @param choicesOrOptions (Optional) array of choices to prompt the user for or additional prompt options. | ||
*/ | ||
prompt(context, dialogId, prompt, choicesOrOptions, options) { | ||
const args = Object.assign({}, Array.isArray(choicesOrOptions) ? { choices: choicesOrOptions } : choicesOrOptions); | ||
if (prompt) { | ||
args.prompt = prompt; | ||
} | ||
return this.begin(context, dialogId, args); | ||
} | ||
/** | ||
* Continues execution of the active dialog, if there is one, by passing the context object to | ||
* its `Dialog.continue()` method. You can check `context.responded` after the call completes | ||
* to determine if a dialog was run and a reply was sent to the user. | ||
* | ||
* **Example usage:** | ||
* | ||
* ```JavaScript | ||
* return dialogs.continue(context).then(() => { | ||
* if (!dialog.responded) { | ||
* return dialogs.begin(context, 'fallback'); | ||
* } | ||
* }); | ||
* ``` | ||
* @param context Context object for the current turn of conversation with the user. | ||
*/ | ||
continue(context) { | ||
try { | ||
if (this.getStack(context).length > 0) { | ||
// Get current dialog instance | ||
const instance = this.getInstance(context); | ||
// Lookup dialog | ||
const dialog = this.find(instance.id); | ||
if (!dialog) { | ||
throw new Error(`DialogSet.continue(): Can't continue dialog. A dialog with an id of '${instance.id}' wasn't found.`); | ||
} | ||
// Check for existence of a continue() method | ||
if (dialog.continue) { | ||
// Continue execution of dialog | ||
return Promise.resolve(dialog.continue(context, this)); | ||
} | ||
else { | ||
// Just end the dialog | ||
return this.end(context); | ||
} | ||
} | ||
else { | ||
return Promise.resolve(); | ||
} | ||
} | ||
catch (err) { | ||
return Promise.reject(err); | ||
} | ||
} | ||
/** | ||
* Ends a dialog by popping it off the stack and returns an optional result to the dialogs | ||
* parent. The parent dialog is the dialog the started the on being ended via a call to | ||
* either [begin()](#begin) or [prompt()](#prompt). | ||
* | ||
* The parent dialog will have its `Dialog.resume()` method invoked with any returned | ||
* result. If the parent dialog hasn't implemented a `resume()` method then it will be | ||
* automatically ended as well and the result passed to its parent. If there are no more | ||
* parent dialogs on the stack then processing of the turn will end. | ||
* | ||
* **Example usage:** | ||
* | ||
* ```JavaScript | ||
* dialogs.add('showUptime', [ | ||
* function (context) { | ||
* const elapsed = new Date().getTime() - started; | ||
* context.reply(`I've been running for ${elapsed / 1000} seconds.`); | ||
* return dialogs.end(context, elapsed); | ||
* } | ||
* ]) | ||
* const started = new Date().getTime(); | ||
* ``` | ||
* @param context Context object for the current turn of conversation with the user. | ||
* @param result (Optional) result to pass to the parent dialogs `Dialog.resume()` method. | ||
*/ | ||
end(context, result) { | ||
try { | ||
// Pop current dialog off the stack | ||
const stack = this.getStack(context); | ||
if (stack.length > 0) { | ||
stack.pop(); | ||
} | ||
// Resume previous dialog | ||
if (stack.length > 0) { | ||
// Get dialog instance | ||
const instance = this.getInstance(context); | ||
// Lookup dialog | ||
const dialog = this.find(instance.id); | ||
if (!dialog) { | ||
throw new Error(`DialogSet.end(): Can't resume previous dialog. A dialog with an id of '${instance.id}' wasn't found.`); | ||
} | ||
// Check for existence of a resumeDialog() method | ||
if (dialog.resume) { | ||
// Return result to previous dialog | ||
return Promise.resolve(dialog.resume(context, this, result)); | ||
} | ||
else { | ||
// Just end the dialog | ||
return this.end(context); | ||
} | ||
} | ||
else { | ||
return Promise.resolve(); | ||
} | ||
} | ||
catch (err) { | ||
return Promise.reject(err); | ||
} | ||
} | ||
/** | ||
* Deletes any existing dialog stack thus cancelling all dialogs on the stack. | ||
* | ||
* **Example usage:** | ||
* | ||
* ```JavaScript | ||
* return dialogs.endAll(context) | ||
* .then(() => dialogs.begin(context, 'addAlarm')); | ||
* ``` | ||
* @param context Context object for the current turn of conversation with the user. | ||
*/ | ||
endAll(context) { | ||
try { | ||
// Cancel any current dialogs | ||
const state = getConversationState(context); | ||
state[this.stackName] = []; | ||
return Promise.resolve(); | ||
} | ||
catch (err) { | ||
return Promise.reject(err); | ||
} | ||
} | ||
/** | ||
* Finds a dialog that was previously added to the set using [add()](#add). | ||
@@ -255,83 +70,4 @@ * | ||
} | ||
/** | ||
* Returns the dialog stack persisted for a conversation. | ||
* | ||
* **Example usage:** | ||
* | ||
* ```JavaScript | ||
* const hasActiveDialog = dialogs.getStack(context).length > 0; | ||
* ``` | ||
* @param context Context object for the current turn of conversation with the user. | ||
*/ | ||
getStack(context) { | ||
const state = getConversationState(context); | ||
if (!Array.isArray(state[this.stackName])) { | ||
state[this.stackName] = []; | ||
} | ||
return state[this.stackName]; | ||
} | ||
/** | ||
* Returns the active dialog instance on the top of the stack. Throws an error if the stack is | ||
* empty so use `dialogs.getStack(context).length > 0` to protect calls where the stack could | ||
* be empty. | ||
* | ||
* **Example usage:** | ||
* | ||
* ```JavaScript | ||
* const dialogState = dialogs.getInstance(context).state; | ||
* ``` | ||
* @param T (Optional) type of dialog state being persisted by the instance. | ||
* @param context Context object for the current turn of conversation with the user. | ||
*/ | ||
getInstance(context) { | ||
const stack = this.getStack(context); | ||
if (stack.length < 1) { | ||
throw new Error(`DialogSet.getInstance(): No active dialog on the stack.`); | ||
} | ||
return stack[stack.length - 1]; | ||
} | ||
/** | ||
* Ends the current dialog and starts a new dialog in its place. This is particularly useful | ||
* for creating loops or redirecting to another dialog. | ||
* | ||
* **Example usage:** | ||
* | ||
* ```JavaScript | ||
* dialogs.add('loop', [ | ||
* function (context, args) { | ||
* dialogs.getInstance(context).state = args; | ||
* return dialogs.begin(context, args.dialogId); | ||
* }, | ||
* function (context) { | ||
* const args = dialogs.getInstance(context).state; | ||
* return dialogs.replace(context, 'loop', args); | ||
* } | ||
* ]); | ||
* ``` | ||
* @param context Context object for the current turn of conversation with the user. | ||
* @param dialogId ID of the new dialog to start. | ||
* @param dialogArgs (Optional) additional argument(s) to pass to the new dialog. | ||
*/ | ||
replace(context, dialogId, dialogArgs) { | ||
try { | ||
// Pop stack | ||
const stack = this.getStack(context); | ||
if (stack.length > 0) { | ||
stack.pop(); | ||
} | ||
// Start replacement dialog | ||
return this.begin(context, dialogId, dialogArgs); | ||
} | ||
catch (err) { | ||
return Promise.reject(err); | ||
} | ||
} | ||
} | ||
exports.DialogSet = DialogSet; | ||
function getConversationState(context) { | ||
if (!context.state.conversation) { | ||
throw new Error(`DialogSet: No conversation state found. Please add a BotStateManager instance to your bots middleware stack.`); | ||
} | ||
return context.state.conversation; | ||
} | ||
//# sourceMappingURL=dialogSet.js.map |
@@ -6,5 +6,8 @@ /** | ||
export * from './prompts/index'; | ||
export * from './compositeControl'; | ||
export * from './control'; | ||
export * from './dialog'; | ||
export * from './dialogContext'; | ||
export * from './dialogSet'; | ||
export * from './waterfall'; | ||
export { FoundChoice, Choice, ChoiceStylerOptions } from 'botbuilder-choices'; | ||
export { FoundChoice, Choice, ChoiceFactoryOptions, FoundDatetime, FindChoicesOptions, ListStyle } from 'botbuilder-prompts'; |
@@ -11,4 +11,11 @@ "use strict"; | ||
__export(require("./prompts/index")); | ||
__export(require("./compositeControl")); | ||
__export(require("./control")); | ||
__export(require("./dialogContext")); | ||
__export(require("./dialogSet")); | ||
__export(require("./waterfall")); | ||
// Re-exporting choice related interfaces used just to avoid TS developers from needing to | ||
// import interfaces from two libraries when working with dialogs. | ||
var botbuilder_prompts_1 = require("botbuilder-prompts"); | ||
exports.ListStyle = botbuilder_prompts_1.ListStyle; | ||
//# sourceMappingURL=index.js.map |
@@ -8,6 +8,5 @@ /** | ||
*/ | ||
import { Attachment } from 'botbuilder'; | ||
import { Dialog } from '../dialog'; | ||
import { DialogSet } from '../dialogSet'; | ||
import { PromptOptions, PromptValidator } from './prompt'; | ||
import { BotContext, Attachment } from 'botbuilder'; | ||
import { DialogContext } from '../dialogContext'; | ||
import { Prompt, PromptOptions, PromptValidator } from './prompt'; | ||
/** | ||
@@ -27,8 +26,8 @@ * Prompts a user to upload attachments like images. By default the prompt will return to the | ||
* dialogs.add('uploadImage', [ | ||
* function (context) { | ||
* return dialogs.prompt(context, 'attachmentPrompt', `Send me image(s)`); | ||
* function (dc) { | ||
* return dc.prompt('attachmentPrompt', `Send me image(s)`); | ||
* }, | ||
* function (context, attachments) { | ||
* context.reply(`Processing ${attachments.length} images.`); | ||
* return dialogs.end(context); | ||
* function (dc, attachments) { | ||
* dc.batch.reply(`Processing ${attachments.length} images.`); | ||
* return dc.end(); | ||
* } | ||
@@ -38,4 +37,4 @@ * ]); | ||
*/ | ||
export declare class AttachmentPrompt implements Dialog { | ||
private validator; | ||
export declare class AttachmentPrompt<C extends BotContext> extends Prompt<C, Attachment[]> { | ||
private prompt; | ||
/** | ||
@@ -47,16 +46,16 @@ * Creates a new instance of the prompt. | ||
* ```JavaScript | ||
* dialogs.add('imagePrompt', new AttachmentPrompt((context, values) => { | ||
* if (values.length < 1) { | ||
* context.reply(`Send me an image or say 'cancel'.`); | ||
* return Prompts.resolve(); | ||
* dialogs.add('imagePrompt', new AttachmentPrompt((dc, values) => { | ||
* if (!Array.isArray(values) || values.length < 1) { | ||
* dc.batch.reply(`Send me an image or say "cancel".`); | ||
* return undefined; | ||
* } else { | ||
* return dialogs.end(context, values); | ||
* return values; | ||
* } | ||
* })); | ||
* ``` | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. If the validator replies with a message no additional retry prompt will be sent. | ||
*/ | ||
constructor(validator?: PromptValidator<Attachment[]> | undefined); | ||
begin(context: BotContext, dialogs: DialogSet, options: PromptOptions): Promise<void>; | ||
continue(context: BotContext, dialogs: DialogSet): Promise<void>; | ||
constructor(validator?: PromptValidator<C, Attachment[]>); | ||
protected onPrompt(dc: DialogContext<C>, options: PromptOptions, isRetry: boolean): Promise<void>; | ||
protected onRecognize(dc: DialogContext<C>, options: PromptOptions): Promise<Attachment[] | undefined>; | ||
} |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const prompt_1 = require("./prompt"); | ||
const prompts = require("botbuilder-prompts"); | ||
/** | ||
@@ -18,8 +19,8 @@ * Prompts a user to upload attachments like images. By default the prompt will return to the | ||
* dialogs.add('uploadImage', [ | ||
* function (context) { | ||
* return dialogs.prompt(context, 'attachmentPrompt', `Send me image(s)`); | ||
* function (dc) { | ||
* return dc.prompt('attachmentPrompt', `Send me image(s)`); | ||
* }, | ||
* function (context, attachments) { | ||
* context.reply(`Processing ${attachments.length} images.`); | ||
* return dialogs.end(context); | ||
* function (dc, attachments) { | ||
* dc.batch.reply(`Processing ${attachments.length} images.`); | ||
* return dc.end(); | ||
* } | ||
@@ -29,3 +30,3 @@ * ]); | ||
*/ | ||
class AttachmentPrompt { | ||
class AttachmentPrompt extends prompt_1.Prompt { | ||
/** | ||
@@ -37,49 +38,28 @@ * Creates a new instance of the prompt. | ||
* ```JavaScript | ||
* dialogs.add('imagePrompt', new AttachmentPrompt((context, values) => { | ||
* if (values.length < 1) { | ||
* context.reply(`Send me an image or say 'cancel'.`); | ||
* return Prompts.resolve(); | ||
* dialogs.add('imagePrompt', new AttachmentPrompt((dc, values) => { | ||
* if (!Array.isArray(values) || values.length < 1) { | ||
* dc.batch.reply(`Send me an image or say "cancel".`); | ||
* return undefined; | ||
* } else { | ||
* return dialogs.end(context, values); | ||
* return values; | ||
* } | ||
* })); | ||
* ``` | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. If the validator replies with a message no additional retry prompt will be sent. | ||
*/ | ||
constructor(validator) { | ||
this.validator = validator; | ||
super(validator); | ||
this.prompt = prompts.createAttachmentPrompt(); | ||
} | ||
begin(context, dialogs, options) { | ||
// Persist options | ||
const instance = dialogs.getInstance(context); | ||
instance.state = options || {}; | ||
// Send initial prompt | ||
if (instance.state.prompt) { | ||
context.reply(prompt_1.formatPrompt(instance.state.prompt, instance.state.speak)); | ||
onPrompt(dc, options, isRetry) { | ||
if (isRetry && options.retryPrompt) { | ||
return this.prompt.prompt(dc.context, options.retryPrompt, options.retrySpeak); | ||
} | ||
else if (options.prompt) { | ||
return this.prompt.prompt(dc.context, options.prompt, options.speak); | ||
} | ||
return Promise.resolve(); | ||
} | ||
continue(context, dialogs) { | ||
// Recognize value | ||
const options = dialogs.getInstance(context).state; | ||
const values = context.request && context.request.attachments ? context.request.attachments : []; | ||
if (this.validator) { | ||
// Call validator for further processing | ||
return Promise.resolve(this.validator(context, values, dialogs)); | ||
} | ||
else if (values.length > 0) { | ||
// Return recognized values | ||
return dialogs.end(context, values); | ||
} | ||
else { | ||
if (options.retryPrompt) { | ||
// Send retry prompt to user | ||
context.reply(prompt_1.formatPrompt(options.retryPrompt, options.retrySpeak)); | ||
} | ||
else if (options.prompt) { | ||
// Send original prompt to user | ||
context.reply(prompt_1.formatPrompt(options.prompt, options.speak)); | ||
} | ||
return Promise.resolve(); | ||
} | ||
onRecognize(dc, options) { | ||
return this.prompt.recognize(dc.context); | ||
} | ||
@@ -86,0 +66,0 @@ } |
@@ -8,34 +8,14 @@ /** | ||
*/ | ||
import { Activity } from 'botbuilder'; | ||
import { Dialog } from '../dialog'; | ||
import { DialogSet } from '../dialogSet'; | ||
import { PromptOptions, PromptValidator } from './prompt'; | ||
import { Choice, ChoiceStylerOptions, FindChoicesOptions, FoundChoice } from 'botbuilder-choices'; | ||
/** | ||
* Controls the way that choices for a `ChoicePrompt` or yes/no options for a `ConfirmPrompt` are | ||
* presented to a user. | ||
*/ | ||
export declare enum ListStyle { | ||
/** Don't include any choices for prompt. */ | ||
none = 0, | ||
/** Automatically select the appropriate style for the current channel. */ | ||
auto = 1, | ||
/** Add choices to prompt as an inline list. */ | ||
inline = 2, | ||
/** Add choices to prompt as a numbered list. */ | ||
list = 3, | ||
/** Add choices to prompt as suggested actions. */ | ||
suggestedAction = 4, | ||
} | ||
import { BotContext } from 'botbuilder'; | ||
import { DialogContext } from '../dialogContext'; | ||
import { Prompt, PromptOptions, PromptValidator } from './prompt'; | ||
import * as prompts from 'botbuilder-prompts'; | ||
/** Additional options that can be used to configure a `ChoicePrompt`. */ | ||
export interface ChoicePromptOptions extends PromptOptions { | ||
/** List of choices to recognize. */ | ||
choices?: (string | Choice)[]; | ||
/** Preferred style of the choices sent to the user. The default value is `ListStyle.auto`. */ | ||
style?: ListStyle; | ||
choices?: (string | prompts.Choice)[]; | ||
} | ||
/** | ||
* Prompts a user to make a selection from a list of choices. By default the prompt will return to | ||
* the calling dialog a `FoundChoice` for the choice the user selected. This can be overridden | ||
* using a custom `PromptValidator`. | ||
* Prompts a user to confirm something with a yes/no response. By default the prompt will return | ||
* to the calling dialog a `boolean` representing the users selection. | ||
* | ||
@@ -52,8 +32,8 @@ * **Example usage:** | ||
* dialogs.add('choiceDemo', [ | ||
* function (context) { | ||
* return dialogs.prompt(context, 'choicePrompt', `choice: select a color`, ['red', 'green', 'blue']); | ||
* function (dc) { | ||
* return dc.prompt('choicePrompt', `choice: select a color`, ['red', 'green', 'blue']); | ||
* }, | ||
* function (context, choice: FoundChoice) { | ||
* context.reply(`Recognized choice: ${JSON.stringify(choice)}`); | ||
* return dialogs.end(context); | ||
* function (dc, choice) { | ||
* dc.batch.reply(`Recognized choice: ${JSON.stringify(choice)}`); | ||
* return dc.end(); | ||
* } | ||
@@ -63,8 +43,4 @@ * ]); | ||
*/ | ||
export declare class ChoicePrompt implements Dialog { | ||
private validator; | ||
/** Additional options passed to the `ChoiceStyler` and used to tweak the style of choices rendered to the user. */ | ||
readonly stylerOptions: ChoiceStylerOptions; | ||
/** Additional options passed to the `recognizeChoices()` function. */ | ||
readonly recognizerOptions: FindChoicesOptions; | ||
export declare class ChoicePrompt<C extends BotContext> extends Prompt<C, prompts.FoundChoice> { | ||
private prompt; | ||
/** | ||
@@ -78,27 +54,24 @@ * Creates a new instance of the prompt. | ||
* ``` | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. If the validator replies with a message no additional retry prompt will be sent. | ||
* @param defaultLocale (Optional) locale to use if `dc.context.request.locale` not specified. Defaults to a value of `en-us`. | ||
*/ | ||
constructor(validator?: PromptValidator<FoundChoice | undefined> | undefined); | ||
begin(context: BotContext, dialogs: DialogSet, options: ChoicePromptOptions): Promise<void>; | ||
continue(context: BotContext, dialogs: DialogSet): Promise<void>; | ||
private sendChoicePrompt(context, dialogs, prompt, speak?); | ||
constructor(validator?: PromptValidator<C, prompts.FoundChoice>, defaultLocale?: string); | ||
/** | ||
* Sets additional options passed to the `ChoiceFactory` and used to tweak the style of choices | ||
* rendered to the user. | ||
* @param options Additional options to set. | ||
*/ | ||
choiceOptions(options: prompts.ChoiceFactoryOptions): this; | ||
/** | ||
* Sets additional options passed to the `recognizeChoices()` function. | ||
* @param options Additional options to set. | ||
*/ | ||
recognizerOptions(options: prompts.FindChoicesOptions): this; | ||
/** | ||
* Sets the style of the choice list rendered to the user when prompting. | ||
* @param listStyle Type of list to render to to user. Defaults to `ListStyle.auto`. | ||
*/ | ||
style(listStyle: prompts.ListStyle): this; | ||
protected onPrompt(dc: DialogContext<C>, options: ChoicePromptOptions, isRetry: boolean): Promise<void>; | ||
protected onRecognize(dc: DialogContext<C>, options: ChoicePromptOptions): Promise<prompts.FoundChoice | undefined>; | ||
} | ||
/** | ||
* Helper function to format a choice prompt for a given `ListStyle`. An activity will be returned | ||
* that can then be sent to the user. | ||
* | ||
* **Example usage:** | ||
* | ||
* ```JavaScript | ||
* const { formatChoicePrompt } = require('botbuilder-dialogs'); | ||
* | ||
* context.reply(formatChoicePrompt(context, ['red', 'green', 'blue'], `Select a color`)); | ||
* ``` | ||
* @param channelOrContext Context for the current turn of conversation with the user or the ID of a channel. This is used when `style == ListStyle.auto`. | ||
* @param choices Array of choices being prompted for. | ||
* @param text (Optional) prompt text to show the user along with the options. | ||
* @param speak (Optional) SSML to speak to the user on channels like Cortana. The messages `inputHint` will be automatically set to `InputHints.expectingInput`. | ||
* @param options (Optional) additional choice styler options used to customize the rendering of the prompts choice list. | ||
* @param style (Optional) list style to use when rendering prompt. Defaults to `ListStyle.auto`. | ||
*/ | ||
export declare function formatChoicePrompt(channelOrContext: string | BotContext, choices: (string | Choice)[], text?: string, speak?: string, options?: ChoiceStylerOptions, style?: ListStyle): Partial<Activity>; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const prompt_1 = require("./prompt"); | ||
const prompts = require("botbuilder-prompts"); | ||
/** | ||
* @module botbuilder-dialogs | ||
*/ | ||
/** | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
const botbuilder_1 = require("botbuilder"); | ||
const botbuilder_choices_1 = require("botbuilder-choices"); | ||
/** | ||
* Controls the way that choices for a `ChoicePrompt` or yes/no options for a `ConfirmPrompt` are | ||
* presented to a user. | ||
*/ | ||
var ListStyle; | ||
(function (ListStyle) { | ||
/** Don't include any choices for prompt. */ | ||
ListStyle[ListStyle["none"] = 0] = "none"; | ||
/** Automatically select the appropriate style for the current channel. */ | ||
ListStyle[ListStyle["auto"] = 1] = "auto"; | ||
/** Add choices to prompt as an inline list. */ | ||
ListStyle[ListStyle["inline"] = 2] = "inline"; | ||
/** Add choices to prompt as a numbered list. */ | ||
ListStyle[ListStyle["list"] = 3] = "list"; | ||
/** Add choices to prompt as suggested actions. */ | ||
ListStyle[ListStyle["suggestedAction"] = 4] = "suggestedAction"; | ||
})(ListStyle = exports.ListStyle || (exports.ListStyle = {})); | ||
/** | ||
* Prompts a user to make a selection from a list of choices. By default the prompt will return to | ||
* the calling dialog a `FoundChoice` for the choice the user selected. This can be overridden | ||
* using a custom `PromptValidator`. | ||
* Prompts a user to confirm something with a yes/no response. By default the prompt will return | ||
* to the calling dialog a `boolean` representing the users selection. | ||
* | ||
@@ -44,8 +19,8 @@ * **Example usage:** | ||
* dialogs.add('choiceDemo', [ | ||
* function (context) { | ||
* return dialogs.prompt(context, 'choicePrompt', `choice: select a color`, ['red', 'green', 'blue']); | ||
* function (dc) { | ||
* return dc.prompt('choicePrompt', `choice: select a color`, ['red', 'green', 'blue']); | ||
* }, | ||
* function (context, choice: FoundChoice) { | ||
* context.reply(`Recognized choice: ${JSON.stringify(choice)}`); | ||
* return dialogs.end(context); | ||
* function (dc, choice) { | ||
* dc.batch.reply(`Recognized choice: ${JSON.stringify(choice)}`); | ||
* return dc.end(); | ||
* } | ||
@@ -55,3 +30,3 @@ * ]); | ||
*/ | ||
class ChoicePrompt { | ||
class ChoicePrompt extends prompt_1.Prompt { | ||
/** | ||
@@ -65,102 +40,49 @@ * Creates a new instance of the prompt. | ||
* ``` | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. If the validator replies with a message no additional retry prompt will be sent. | ||
* @param defaultLocale (Optional) locale to use if `dc.context.request.locale` not specified. Defaults to a value of `en-us`. | ||
*/ | ||
constructor(validator) { | ||
this.validator = validator; | ||
/** Additional options passed to the `ChoiceStyler` and used to tweak the style of choices rendered to the user. */ | ||
this.stylerOptions = {}; | ||
/** Additional options passed to the `recognizeChoices()` function. */ | ||
this.recognizerOptions = {}; | ||
constructor(validator, defaultLocale) { | ||
super(validator); | ||
this.prompt = prompts.createChoicePrompt(undefined, defaultLocale); | ||
} | ||
begin(context, dialogs, options) { | ||
// Persist options | ||
const instance = dialogs.getInstance(context); | ||
instance.state = options || {}; | ||
// Send initial prompt | ||
if (instance.state.prompt) { | ||
return this.sendChoicePrompt(context, dialogs, instance.state.prompt, instance.state.speak); | ||
} | ||
else { | ||
return Promise.resolve(); | ||
} | ||
/** | ||
* Sets additional options passed to the `ChoiceFactory` and used to tweak the style of choices | ||
* rendered to the user. | ||
* @param options Additional options to set. | ||
*/ | ||
choiceOptions(options) { | ||
Object.assign(this.prompt.choiceOptions, options); | ||
return this; | ||
} | ||
continue(context, dialogs) { | ||
// Recognize value | ||
const options = dialogs.getInstance(context).state; | ||
const utterance = context.request && context.request.text ? context.request.text : ''; | ||
const results = botbuilder_choices_1.recognizeChoices(utterance, options.choices || [], this.recognizerOptions); | ||
const value = results.length > 0 ? results[0].resolution : undefined; | ||
if (this.validator) { | ||
// Call validator for further processing | ||
return Promise.resolve(this.validator(context, value, dialogs)); | ||
} | ||
else if (value) { | ||
// Return recognized choice | ||
return dialogs.end(context, value); | ||
} | ||
else if (options.retryPrompt) { | ||
// Send retry prompt to user | ||
return this.sendChoicePrompt(context, dialogs, options.retryPrompt, options.retrySpeak); | ||
} | ||
else if (options.prompt) { | ||
// Send original prompt to user | ||
return this.sendChoicePrompt(context, dialogs, options.prompt, options.speak); | ||
} | ||
else { | ||
return Promise.resolve(); | ||
} | ||
/** | ||
* Sets additional options passed to the `recognizeChoices()` function. | ||
* @param options Additional options to set. | ||
*/ | ||
recognizerOptions(options) { | ||
Object.assign(this.prompt.recognizerOptions, options); | ||
return this; | ||
} | ||
sendChoicePrompt(context, dialogs, prompt, speak) { | ||
if (typeof prompt === 'string') { | ||
const options = dialogs.getInstance(context).state; | ||
context.reply(formatChoicePrompt(context, options.choices || [], prompt, speak, this.stylerOptions, options.style)); | ||
/** | ||
* Sets the style of the choice list rendered to the user when prompting. | ||
* @param listStyle Type of list to render to to user. Defaults to `ListStyle.auto`. | ||
*/ | ||
style(listStyle) { | ||
this.prompt.style = listStyle; | ||
return this; | ||
} | ||
onPrompt(dc, options, isRetry) { | ||
const choices = options.choices || []; | ||
if (isRetry && options.retryPrompt) { | ||
return this.prompt.prompt(dc.context, choices, options.retryPrompt, options.retrySpeak); | ||
} | ||
else { | ||
context.reply(prompt); | ||
else if (choices.length || options.prompt) { | ||
return this.prompt.prompt(dc.context, choices, options.prompt, options.speak); | ||
} | ||
return Promise.resolve(); | ||
} | ||
onRecognize(dc, options) { | ||
return this.prompt.recognize(dc.context, options.choices || []); | ||
} | ||
} | ||
exports.ChoicePrompt = ChoicePrompt; | ||
/** | ||
* Helper function to format a choice prompt for a given `ListStyle`. An activity will be returned | ||
* that can then be sent to the user. | ||
* | ||
* **Example usage:** | ||
* | ||
* ```JavaScript | ||
* const { formatChoicePrompt } = require('botbuilder-dialogs'); | ||
* | ||
* context.reply(formatChoicePrompt(context, ['red', 'green', 'blue'], `Select a color`)); | ||
* ``` | ||
* @param channelOrContext Context for the current turn of conversation with the user or the ID of a channel. This is used when `style == ListStyle.auto`. | ||
* @param choices Array of choices being prompted for. | ||
* @param text (Optional) prompt text to show the user along with the options. | ||
* @param speak (Optional) SSML to speak to the user on channels like Cortana. The messages `inputHint` will be automatically set to `InputHints.expectingInput`. | ||
* @param options (Optional) additional choice styler options used to customize the rendering of the prompts choice list. | ||
* @param style (Optional) list style to use when rendering prompt. Defaults to `ListStyle.auto`. | ||
*/ | ||
function formatChoicePrompt(channelOrContext, choices, text, speak, options, style) { | ||
switch (style) { | ||
case ListStyle.auto: | ||
default: | ||
return botbuilder_choices_1.ChoiceStyler.forChannel(channelOrContext, choices, text, speak, options); | ||
case ListStyle.inline: | ||
return botbuilder_choices_1.ChoiceStyler.inline(choices, text, speak, options); | ||
case ListStyle.list: | ||
return botbuilder_choices_1.ChoiceStyler.list(choices, text, speak, options); | ||
case ListStyle.suggestedAction: | ||
return botbuilder_choices_1.ChoiceStyler.suggestedAction(choices, text, speak, options); | ||
case ListStyle.none: | ||
const p = { type: 'message', text: text || '' }; | ||
if (speak) { | ||
p.speak = speak; | ||
} | ||
if (!p.inputHint) { | ||
p.inputHint = botbuilder_1.InputHints.ExpectingInput; | ||
} | ||
return p; | ||
} | ||
} | ||
exports.formatChoicePrompt = formatChoicePrompt; | ||
//# sourceMappingURL=choicePrompt.js.map |
@@ -8,17 +8,6 @@ /** | ||
*/ | ||
import { Activity } from 'botbuilder'; | ||
import { Dialog } from '../dialog'; | ||
import { DialogSet } from '../dialogSet'; | ||
import { PromptOptions, PromptValidator } from './prompt'; | ||
import { ListStyle } from './choicePrompt'; | ||
import { ChoiceStylerOptions, Choice } from 'botbuilder-choices'; | ||
/** Map of `ConfirmPrompt` choices for each locale the bot supports. */ | ||
export interface ConfirmChoices { | ||
[locale: string]: (string | Choice)[]; | ||
} | ||
/** Additional options that can be used to configure a `ChoicePrompt`. */ | ||
export interface ConfirmPromptOptions extends PromptOptions { | ||
/** Preferred style of the yes/no choices sent to the user. The default value is `ListStyle.auto`. */ | ||
style?: ListStyle; | ||
} | ||
import { BotContext } from 'botbuilder'; | ||
import { DialogContext } from '../dialogContext'; | ||
import { Prompt, PromptOptions, PromptValidator } from './prompt'; | ||
import * as prompts from 'botbuilder-prompts'; | ||
/** | ||
@@ -38,8 +27,8 @@ * Prompts a user to confirm something with a yes/no response. By default the prompt will return | ||
* dialogs.add('confirmDemo', [ | ||
* function (context) { | ||
* return dialogs.prompt(context, 'confirmPrompt', `confirm: answer "yes" or "no"`); | ||
* function (dc) { | ||
* return dc.prompt('confirmPrompt', `confirm: answer "yes" or "no"`); | ||
* }, | ||
* function (context, value) { | ||
* context.reply(`Recognized value: ${value}`); | ||
* return dialogs.end(context); | ||
* function (dc, value) { | ||
* dc.batch.reply(`Recognized value: ${value}`); | ||
* return dc.end(); | ||
* } | ||
@@ -49,4 +38,4 @@ * ]); | ||
*/ | ||
export declare class ConfirmPrompt implements Dialog { | ||
private validator; | ||
export declare class ConfirmPrompt<C extends BotContext> extends Prompt<C, boolean> { | ||
private prompt; | ||
/** | ||
@@ -66,5 +55,3 @@ * Allows for the localization of the confirm prompts yes/no choices to other locales besides | ||
*/ | ||
static choices: ConfirmChoices; | ||
/** Additional options passed to the `ChoiceStyler` and used to tweak the style of yes/no choices rendered to the user. */ | ||
readonly stylerOptions: ChoiceStylerOptions; | ||
static choices: prompts.ConfirmChoices; | ||
/** | ||
@@ -76,17 +63,28 @@ * Creates a new instance of the prompt. | ||
* ```JavaScript | ||
* dialogs.add('confirmPrompt', new ConfirmPrompt((context, value) => { | ||
* dialogs.add('confirmPrompt', new ConfirmPrompt((dc, value) => { | ||
* if (value === undefined) { | ||
* context.reply(`Please answer with "yes" or "no".`); | ||
* return Prompts.resolve(); | ||
* dc.batch.reply(`Invalid answer. Answer with "yes" or "no".`); | ||
* return undefined; | ||
* } else { | ||
* return dialogs.end(context, values); | ||
* return value; | ||
* } | ||
* })); | ||
* ``` | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. If the validator replies with a message no additional retry prompt will be sent. | ||
* @param defaultLocale (Optional) locale to use if `dc.context.request.locale` not specified. Defaults to a value of `en-us`. | ||
*/ | ||
constructor(validator?: PromptValidator<boolean | undefined> | undefined); | ||
begin(context: BotContext, dialogs: DialogSet, options: ConfirmPromptOptions): Promise<void>; | ||
continue(context: BotContext, dialogs: DialogSet): Promise<void>; | ||
protected sendChoicePrompt(context: BotContext, dialogs: DialogSet, prompt: string | Partial<Activity>, speak?: string): Promise<void>; | ||
constructor(validator?: PromptValidator<C, boolean>, defaultLocale?: string); | ||
/** | ||
* Sets additional options passed to the `ChoiceFactory` and used to tweak the style of choices | ||
* rendered to the user. | ||
* @param options Additional options to set. | ||
*/ | ||
choiceOptions(options: prompts.ChoiceFactoryOptions): this; | ||
/** | ||
* Sets the style of the yes/no choices rendered to the user when prompting. | ||
* @param listStyle Type of list to render to to user. Defaults to `ListStyle.auto`. | ||
*/ | ||
style(listStyle: prompts.ListStyle): this; | ||
protected onPrompt(dc: DialogContext<C>, options: PromptOptions, isRetry: boolean): Promise<void>; | ||
protected onRecognize(dc: DialogContext<C>, options: PromptOptions): Promise<boolean | undefined>; | ||
} |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const choicePrompt_1 = require("./choicePrompt"); | ||
const Recognizers = require("@microsoft/recognizers-text-choice"); | ||
const prompt_1 = require("./prompt"); | ||
const prompts = require("botbuilder-prompts"); | ||
/** | ||
@@ -19,8 +19,8 @@ * Prompts a user to confirm something with a yes/no response. By default the prompt will return | ||
* dialogs.add('confirmDemo', [ | ||
* function (context) { | ||
* return dialogs.prompt(context, 'confirmPrompt', `confirm: answer "yes" or "no"`); | ||
* function (dc) { | ||
* return dc.prompt('confirmPrompt', `confirm: answer "yes" or "no"`); | ||
* }, | ||
* function (context, value) { | ||
* context.reply(`Recognized value: ${value}`); | ||
* return dialogs.end(context); | ||
* function (dc, value) { | ||
* dc.batch.reply(`Recognized value: ${value}`); | ||
* return dc.end(); | ||
* } | ||
@@ -30,3 +30,3 @@ * ]); | ||
*/ | ||
class ConfirmPrompt { | ||
class ConfirmPrompt extends prompt_1.Prompt { | ||
/** | ||
@@ -38,72 +38,48 @@ * Creates a new instance of the prompt. | ||
* ```JavaScript | ||
* dialogs.add('confirmPrompt', new ConfirmPrompt((context, value) => { | ||
* dialogs.add('confirmPrompt', new ConfirmPrompt((dc, value) => { | ||
* if (value === undefined) { | ||
* context.reply(`Please answer with "yes" or "no".`); | ||
* return Prompts.resolve(); | ||
* dc.batch.reply(`Invalid answer. Answer with "yes" or "no".`); | ||
* return undefined; | ||
* } else { | ||
* return dialogs.end(context, values); | ||
* return value; | ||
* } | ||
* })); | ||
* ``` | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. If the validator replies with a message no additional retry prompt will be sent. | ||
* @param defaultLocale (Optional) locale to use if `dc.context.request.locale` not specified. Defaults to a value of `en-us`. | ||
*/ | ||
constructor(validator) { | ||
this.validator = validator; | ||
this.stylerOptions = { includeNumbers: false }; | ||
constructor(validator, defaultLocale) { | ||
super(validator); | ||
this.prompt = prompts.createConfirmPrompt(undefined, defaultLocale); | ||
this.prompt.choices = ConfirmPrompt.choices; | ||
} | ||
begin(context, dialogs, options) { | ||
// Persist options | ||
const instance = dialogs.getInstance(context); | ||
instance.state = options || {}; | ||
// Send initial prompt | ||
if (instance.state.prompt) { | ||
return this.sendChoicePrompt(context, dialogs, instance.state.prompt, instance.state.speak); | ||
} | ||
else { | ||
return Promise.resolve(); | ||
} | ||
/** | ||
* Sets additional options passed to the `ChoiceFactory` and used to tweak the style of choices | ||
* rendered to the user. | ||
* @param options Additional options to set. | ||
*/ | ||
choiceOptions(options) { | ||
Object.assign(this.prompt.choiceOptions, options); | ||
return this; | ||
} | ||
continue(context, dialogs) { | ||
// Recognize value | ||
const options = dialogs.getInstance(context).state; | ||
const utterance = context.request && context.request.text ? context.request.text : ''; | ||
const results = Recognizers.recognizeBoolean(utterance, 'en-us'); | ||
const value = results.length > 0 && results[0].resolution ? results[0].resolution.value : undefined; | ||
if (this.validator) { | ||
// Call validator for further processing | ||
return Promise.resolve(this.validator(context, value, dialogs)); | ||
/** | ||
* Sets the style of the yes/no choices rendered to the user when prompting. | ||
* @param listStyle Type of list to render to to user. Defaults to `ListStyle.auto`. | ||
*/ | ||
style(listStyle) { | ||
this.prompt.style = listStyle; | ||
return this; | ||
} | ||
onPrompt(dc, options, isRetry) { | ||
if (isRetry && options.retryPrompt) { | ||
return this.prompt.prompt(dc.context, options.retryPrompt, options.retrySpeak); | ||
} | ||
else if (typeof value === 'boolean') { | ||
// Return recognized value | ||
return dialogs.end(context, value); | ||
} | ||
else if (options.retryPrompt) { | ||
// Send retry prompt to user | ||
return this.sendChoicePrompt(context, dialogs, options.retryPrompt, options.retrySpeak); | ||
} | ||
else if (options.prompt) { | ||
// Send original prompt to user | ||
return this.sendChoicePrompt(context, dialogs, options.prompt, options.speak); | ||
return this.prompt.prompt(dc.context, options.prompt, options.speak); | ||
} | ||
else { | ||
return Promise.resolve(); | ||
} | ||
} | ||
sendChoicePrompt(context, dialogs, prompt, speak) { | ||
if (typeof prompt === 'string') { | ||
// Get locale specific choices | ||
let locale = context.request && context.request.locale ? context.request.locale.toLowerCase() : '*'; | ||
if (!ConfirmPrompt.choices.hasOwnProperty(locale)) { | ||
locale = '*'; | ||
} | ||
const choices = ConfirmPrompt.choices[locale]; | ||
// Reply with formatted prompt | ||
const style = dialogs.getInstance(context).state.style; | ||
context.reply(choicePrompt_1.formatChoicePrompt(context, choices, prompt, speak, this.stylerOptions, style)); | ||
} | ||
else { | ||
context.reply(prompt); | ||
} | ||
return Promise.resolve(); | ||
} | ||
onRecognize(dc, options) { | ||
return this.prompt.recognize(dc.context); | ||
} | ||
} | ||
@@ -110,0 +86,0 @@ /** |
@@ -8,26 +8,7 @@ /** | ||
*/ | ||
import { Dialog } from '../dialog'; | ||
import { DialogSet } from '../dialogSet'; | ||
import { PromptOptions, PromptValidator } from './prompt'; | ||
import { BotContext } from 'botbuilder'; | ||
import { DialogContext } from '../dialogContext'; | ||
import { Prompt, PromptOptions, PromptValidator } from './prompt'; | ||
import * as prompts from 'botbuilder-prompts'; | ||
/** | ||
* Datetime result returned by `DatetimePrompt`. For more details see the LUIS docs for | ||
* [builtin.datetimev2](https://docs.microsoft.com/en-us/azure/cognitive-services/luis/luis-reference-prebuilt-entities#builtindatetimev2). | ||
*/ | ||
export interface FoundDatetime { | ||
/** | ||
* TIMEX expression representing ambiguity of the recognized time. | ||
*/ | ||
timex: string; | ||
/** | ||
* Type of time recognized. Possible values are 'date', 'time', 'datetime', 'daterange', | ||
* 'timerange', 'datetimerange', 'duration', or 'set'. | ||
*/ | ||
type: string; | ||
/** | ||
* Value of the specified [type](#type) that's a reasonable approximation given the ambiguity | ||
* of the [timex](#timex). | ||
*/ | ||
value: string; | ||
} | ||
/** | ||
* Prompts a user to enter a datetime expression. By default the prompt will return to the | ||
@@ -46,8 +27,8 @@ * calling dialog a `FoundDatetime[]` but this can be overridden using a custom `PromptValidator`. | ||
* dialogs.add('datetimeDemo', [ | ||
* function (context) { | ||
* return dialogs.prompt(context, 'datetimePrompt', `datetime: enter a datetime`); | ||
* function (dc) { | ||
* return dc.prompt('datetimePrompt', `datetime: enter a datetime`); | ||
* }, | ||
* function (context, values) { | ||
* context.reply(`Recognized values: ${JSON.stringify(values)}`); | ||
* return dialogs.end(context); | ||
* function (dc, values) { | ||
* dc.batch.reply(`Recognized values: ${JSON.stringify(values)}`); | ||
* return dc.end(); | ||
* } | ||
@@ -57,4 +38,4 @@ * ]); | ||
*/ | ||
export declare class DatetimePrompt implements Dialog { | ||
private validator; | ||
export declare class DatetimePrompt<C extends BotContext> extends Prompt<C, prompts.FoundDatetime[]> { | ||
private prompt; | ||
/** | ||
@@ -66,20 +47,21 @@ * Creates a new instance of the prompt. | ||
* ```JavaScript | ||
* dialogs.add('timePrompt', new DatetimePrompt((context, values) => { | ||
* dialogs.add('timePrompt', new DatetimePrompt((dc, values) => { | ||
* try { | ||
* if (values.length < 0) { throw new Error('missing time') } | ||
* if (!Array.isArray(values) || values.length < 0) { throw new Error('missing time') } | ||
* if (values[0].type !== 'datetime') { throw new Error('unsupported type') } | ||
* const value = new Date(values[0].value); | ||
* if (value.getTime() < new Date().getTime()) { throw new Error('in the past') } | ||
* return dialogs.end(context, value); | ||
* return value; | ||
* } catch (err) { | ||
* context.reply(`Please enter a valid time in the future like "tomorrow at 9am" or say "cancel".`); | ||
* return Promise.resolve(); | ||
* dc.batch.reply(`Invalid time. Answer with a time in the future like "tomorrow at 9am" or say "cancel".`); | ||
* return undefined; | ||
* } | ||
* })); | ||
* ``` | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. If the validator replies with a message no additional retry prompt will be sent. | ||
* @param defaultLocale (Optional) locale to use if `dc.context.request.locale` not specified. Defaults to a value of `en-us`. | ||
*/ | ||
constructor(validator?: PromptValidator<FoundDatetime[]> | undefined); | ||
begin(context: BotContext, dialogs: DialogSet, options: PromptOptions): Promise<void>; | ||
continue(context: BotContext, dialogs: DialogSet): Promise<void>; | ||
constructor(validator?: PromptValidator<C, prompts.FoundDatetime[]>, defaultLocale?: string); | ||
protected onPrompt(dc: DialogContext<C>, options: PromptOptions, isRetry: boolean): Promise<void>; | ||
protected onRecognize(dc: DialogContext<C>, options: PromptOptions): Promise<prompts.FoundDatetime[] | undefined>; | ||
} |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const prompt_1 = require("./prompt"); | ||
const Recognizers = require("@microsoft/recognizers-text-date-time"); | ||
const prompts = require("botbuilder-prompts"); | ||
/** | ||
@@ -19,8 +19,8 @@ * Prompts a user to enter a datetime expression. By default the prompt will return to the | ||
* dialogs.add('datetimeDemo', [ | ||
* function (context) { | ||
* return dialogs.prompt(context, 'datetimePrompt', `datetime: enter a datetime`); | ||
* function (dc) { | ||
* return dc.prompt('datetimePrompt', `datetime: enter a datetime`); | ||
* }, | ||
* function (context, values) { | ||
* context.reply(`Recognized values: ${JSON.stringify(values)}`); | ||
* return dialogs.end(context); | ||
* function (dc, values) { | ||
* dc.batch.reply(`Recognized values: ${JSON.stringify(values)}`); | ||
* return dc.end(); | ||
* } | ||
@@ -30,3 +30,3 @@ * ]); | ||
*/ | ||
class DatetimePrompt { | ||
class DatetimePrompt extends prompt_1.Prompt { | ||
/** | ||
@@ -38,55 +38,33 @@ * Creates a new instance of the prompt. | ||
* ```JavaScript | ||
* dialogs.add('timePrompt', new DatetimePrompt((context, values) => { | ||
* dialogs.add('timePrompt', new DatetimePrompt((dc, values) => { | ||
* try { | ||
* if (values.length < 0) { throw new Error('missing time') } | ||
* if (!Array.isArray(values) || values.length < 0) { throw new Error('missing time') } | ||
* if (values[0].type !== 'datetime') { throw new Error('unsupported type') } | ||
* const value = new Date(values[0].value); | ||
* if (value.getTime() < new Date().getTime()) { throw new Error('in the past') } | ||
* return dialogs.end(context, value); | ||
* return value; | ||
* } catch (err) { | ||
* context.reply(`Please enter a valid time in the future like "tomorrow at 9am" or say "cancel".`); | ||
* return Promise.resolve(); | ||
* dc.batch.reply(`Invalid time. Answer with a time in the future like "tomorrow at 9am" or say "cancel".`); | ||
* return undefined; | ||
* } | ||
* })); | ||
* ``` | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. If the validator replies with a message no additional retry prompt will be sent. | ||
* @param defaultLocale (Optional) locale to use if `dc.context.request.locale` not specified. Defaults to a value of `en-us`. | ||
*/ | ||
constructor(validator) { | ||
this.validator = validator; | ||
constructor(validator, defaultLocale) { | ||
super(validator); | ||
this.prompt = prompts.createDatetimePrompt(undefined, defaultLocale); | ||
} | ||
begin(context, dialogs, options) { | ||
// Persist options | ||
const instance = dialogs.getInstance(context); | ||
instance.state = options || {}; | ||
// Send initial prompt | ||
if (instance.state.prompt) { | ||
context.reply(prompt_1.formatPrompt(instance.state.prompt, instance.state.speak)); | ||
onPrompt(dc, options, isRetry) { | ||
if (isRetry && options.retryPrompt) { | ||
return this.prompt.prompt(dc.context, options.retryPrompt, options.retrySpeak); | ||
} | ||
else if (options.prompt) { | ||
return this.prompt.prompt(dc.context, options.prompt, options.speak); | ||
} | ||
return Promise.resolve(); | ||
} | ||
continue(context, dialogs) { | ||
// Recognize value | ||
const options = dialogs.getInstance(context).state; | ||
const utterance = context.request && context.request.text ? context.request.text : ''; | ||
const results = Recognizers.recognizeDateTime(utterance, 'en-us'); | ||
const value = results.length > 0 && results[0].resolution ? (results[0].resolution.values || []) : []; | ||
if (this.validator) { | ||
// Call validator for further processing | ||
return Promise.resolve(this.validator(context, value, dialogs)); | ||
} | ||
else if (value.length > 0) { | ||
// Return recognized value | ||
return dialogs.end(context, value); | ||
} | ||
else { | ||
if (options.retryPrompt) { | ||
// Send retry prompt to user | ||
context.reply(prompt_1.formatPrompt(options.retryPrompt, options.retrySpeak)); | ||
} | ||
else if (options.prompt) { | ||
// Send original prompt to user | ||
context.reply(prompt_1.formatPrompt(options.prompt, options.speak)); | ||
} | ||
return Promise.resolve(); | ||
} | ||
onRecognize(dc, options) { | ||
return this.prompt.recognize(dc.context); | ||
} | ||
@@ -93,0 +71,0 @@ } |
@@ -8,5 +8,5 @@ /** | ||
*/ | ||
import { Dialog } from '../dialog'; | ||
import { DialogSet } from '../dialogSet'; | ||
import { PromptOptions, PromptValidator } from './prompt'; | ||
import { BotContext } from 'botbuilder'; | ||
import { DialogContext } from '../dialogContext'; | ||
import { Prompt, PromptOptions, PromptValidator } from './prompt'; | ||
/** | ||
@@ -26,8 +26,8 @@ * Prompts a user to enter a number. By default the prompt will return to the calling dialog | ||
* dialogs.add('numberDemo', [ | ||
* function (context) { | ||
* return dialogs.prompt(context, 'numberPrompt', `number: enter a number`); | ||
* function (dc) { | ||
* return dc.prompt('numberPrompt', `number: enter a number`); | ||
* }, | ||
* function (context, value) { | ||
* context.reply(`Recognized value: ${value}`); | ||
* return dialogs.end(context); | ||
* function (dc, value) { | ||
* dc.batch.reply(`Recognized value: ${value}`); | ||
* return dc.end(); | ||
* } | ||
@@ -37,4 +37,4 @@ * ]); | ||
*/ | ||
export declare class NumberPrompt implements Dialog { | ||
private validator; | ||
export declare class NumberPrompt<C extends BotContext> extends Prompt<C, number> { | ||
private prompt; | ||
/** | ||
@@ -46,16 +46,17 @@ * Creates a new instance of the prompt. | ||
* ```JavaScript | ||
* dialogs.add('agePrompt', new NumberPrompt((context, value) => { | ||
* dialogs.add('agePrompt', new NumberPrompt((dc, value) => { | ||
* if (value === undefined || value < 1 || value > 110) { | ||
* context.reply(`Please enter a valid age between 1 and 110.`); | ||
* return Promise.resolve(); | ||
* dc.batch.reply(`Invalid age. Only ages between 1 and 110 are allowed.`); | ||
* return undefined; | ||
* } else { | ||
* return dialogs.end(context, value); | ||
* return value; | ||
* } | ||
* })); | ||
* ``` | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. If the validator replies with a message no additional retry prompt will be sent. | ||
* @param defaultLocale (Optional) locale to use if `dc.context.request.locale` not specified. Defaults to a value of `en-us`. | ||
*/ | ||
constructor(validator?: PromptValidator<number | undefined> | undefined); | ||
begin(context: BotContext, dialogs: DialogSet, options: PromptOptions): Promise<void>; | ||
continue(context: BotContext, dialogs: DialogSet): Promise<void>; | ||
constructor(validator?: PromptValidator<C, number>, defaultLocale?: string); | ||
protected onPrompt(dc: DialogContext<C>, options: PromptOptions, isRetry: boolean): Promise<void>; | ||
protected onRecognize(dc: DialogContext<C>, options: PromptOptions): Promise<number | undefined>; | ||
} |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const prompt_1 = require("./prompt"); | ||
const Recognizers = require("@microsoft/recognizers-text-number"); | ||
const prompts = require("botbuilder-prompts"); | ||
/** | ||
@@ -19,8 +19,8 @@ * Prompts a user to enter a number. By default the prompt will return to the calling dialog | ||
* dialogs.add('numberDemo', [ | ||
* function (context) { | ||
* return dialogs.prompt(context, 'numberPrompt', `number: enter a number`); | ||
* function (dc) { | ||
* return dc.prompt('numberPrompt', `number: enter a number`); | ||
* }, | ||
* function (context, value) { | ||
* context.reply(`Recognized value: ${value}`); | ||
* return dialogs.end(context); | ||
* function (dc, value) { | ||
* dc.batch.reply(`Recognized value: ${value}`); | ||
* return dc.end(); | ||
* } | ||
@@ -30,3 +30,3 @@ * ]); | ||
*/ | ||
class NumberPrompt { | ||
class NumberPrompt extends prompt_1.Prompt { | ||
/** | ||
@@ -38,51 +38,29 @@ * Creates a new instance of the prompt. | ||
* ```JavaScript | ||
* dialogs.add('agePrompt', new NumberPrompt((context, value) => { | ||
* dialogs.add('agePrompt', new NumberPrompt((dc, value) => { | ||
* if (value === undefined || value < 1 || value > 110) { | ||
* context.reply(`Please enter a valid age between 1 and 110.`); | ||
* return Promise.resolve(); | ||
* dc.batch.reply(`Invalid age. Only ages between 1 and 110 are allowed.`); | ||
* return undefined; | ||
* } else { | ||
* return dialogs.end(context, value); | ||
* return value; | ||
* } | ||
* })); | ||
* ``` | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. If the validator replies with a message no additional retry prompt will be sent. | ||
* @param defaultLocale (Optional) locale to use if `dc.context.request.locale` not specified. Defaults to a value of `en-us`. | ||
*/ | ||
constructor(validator) { | ||
this.validator = validator; | ||
constructor(validator, defaultLocale) { | ||
super(validator); | ||
this.prompt = prompts.createNumberPrompt(undefined, defaultLocale); | ||
} | ||
begin(context, dialogs, options) { | ||
// Persist options | ||
const instance = dialogs.getInstance(context); | ||
instance.state = options || {}; | ||
// Send initial prompt | ||
if (instance.state.prompt) { | ||
context.reply(prompt_1.formatPrompt(instance.state.prompt, instance.state.speak)); | ||
onPrompt(dc, options, isRetry) { | ||
if (isRetry && options.retryPrompt) { | ||
return this.prompt.prompt(dc.context, options.retryPrompt, options.retrySpeak); | ||
} | ||
else if (options.prompt) { | ||
return this.prompt.prompt(dc.context, options.prompt, options.speak); | ||
} | ||
return Promise.resolve(); | ||
} | ||
continue(context, dialogs) { | ||
// Recognize value | ||
const options = dialogs.getInstance(context).state; | ||
const utterance = context.request && context.request.text ? context.request.text : ''; | ||
const results = Recognizers.recognizeNumber(utterance, 'en-us'); | ||
const value = results.length > 0 && results[0].resolution ? parseFloat(results[0].resolution.value) : undefined; | ||
if (this.validator) { | ||
// Call validator for further processing | ||
return Promise.resolve(this.validator(context, value, dialogs)); | ||
} | ||
else if (typeof value === 'number') { | ||
// Return recognized value | ||
return dialogs.end(context, value); | ||
} | ||
else { | ||
if (options.retryPrompt) { | ||
// Send retry prompt to user | ||
context.reply(prompt_1.formatPrompt(options.retryPrompt, options.retrySpeak)); | ||
} | ||
else if (options.prompt) { | ||
// Send original prompt to user | ||
context.reply(prompt_1.formatPrompt(options.prompt, options.speak)); | ||
} | ||
return Promise.resolve(); | ||
} | ||
onRecognize(dc, options) { | ||
return this.prompt.recognize(dc.context); | ||
} | ||
@@ -89,0 +67,0 @@ } |
@@ -8,4 +8,5 @@ /** | ||
*/ | ||
import { Activity, Promiseable } from 'botbuilder'; | ||
import { DialogSet } from '../dialogSet'; | ||
import { BotContext, Activity, Promiseable } from 'botbuilder'; | ||
import { DialogContext } from '../dialogContext'; | ||
import { Control } from '../control'; | ||
/** Basic configuration options supported by all prompts. */ | ||
@@ -27,21 +28,16 @@ export interface PromptOptions { | ||
* recognized. | ||
* @param T Possible types for `value` arg. | ||
* @param PromptValidator.context Context object for the current turn of conversation with the user. | ||
* @param C Type of dialog context object passed to validator. | ||
* @param R Type of value that will recognized and passed to the validator as input. | ||
* @param O Type of output that will be returned by the validator. This can be changed from the input type by the validator. | ||
* @param PromptValidator.context Dialog context for the current turn of conversation with the user. | ||
* @param PromptValidator.value The value that was recognized or wasn't recognized. Depending on the prompt this can be either undefined or an empty array to indicate an unrecognized value. | ||
* @param PromptValidator.dialogs The parent dialog set. | ||
*/ | ||
export declare type PromptValidator<T> = (context: BotContext, value: T, dialogs: DialogSet) => Promiseable<void>; | ||
/** | ||
* Helper function to properly format a prompt sent to a user. | ||
* | ||
* **Example usage:** | ||
* | ||
* ```JavaScript | ||
* const { formatPrompt } = require('botbuilder-dialogs'); | ||
* | ||
* context.reply(formatPrompt(`Hi... What's your name?`, `What is your name?`)); | ||
* ``` | ||
* @param prompt Activity or text to prompt the user with. If prompt is a `string` then an activity of type `message` will be created. | ||
* @param speak (Optional) SSML to speak to the user on channels like Cortana. The messages `inputHint` will be automatically set to `InputHints.expectingInput`. | ||
*/ | ||
export declare function formatPrompt(prompt: string | Partial<Activity>, speak?: string): Partial<Activity>; | ||
export declare type PromptValidator<C extends BotContext, R> = (dc: DialogContext<C>, value: R | undefined) => Promiseable<any>; | ||
export declare abstract class Prompt<C extends BotContext, T> extends Control<C> { | ||
private validator; | ||
constructor(validator?: PromptValidator<C, T>); | ||
protected abstract onPrompt(dc: DialogContext<C>, options: PromptOptions, isRetry: boolean): Promise<any>; | ||
protected abstract onRecognize(dc: DialogContext<C>, options: PromptOptions): Promise<T | undefined>; | ||
dialogBegin(dc: DialogContext<C>, options: PromptOptions): Promise<any>; | ||
dialogContinue(dc: DialogContext<C>): Promise<any>; | ||
} |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/** | ||
* @module botbuilder-dialogs | ||
*/ | ||
/** | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
const botbuilder_1 = require("botbuilder"); | ||
/** | ||
* Helper function to properly format a prompt sent to a user. | ||
* | ||
* **Example usage:** | ||
* | ||
* ```JavaScript | ||
* const { formatPrompt } = require('botbuilder-dialogs'); | ||
* | ||
* context.reply(formatPrompt(`Hi... What's your name?`, `What is your name?`)); | ||
* ``` | ||
* @param prompt Activity or text to prompt the user with. If prompt is a `string` then an activity of type `message` will be created. | ||
* @param speak (Optional) SSML to speak to the user on channels like Cortana. The messages `inputHint` will be automatically set to `InputHints.expectingInput`. | ||
*/ | ||
function formatPrompt(prompt, speak) { | ||
const p = typeof prompt === 'string' ? { type: 'message', text: prompt } : prompt; | ||
if (speak) { | ||
p.speak = speak; | ||
const control_1 = require("../control"); | ||
class Prompt extends control_1.Control { | ||
constructor(validator) { | ||
super(); | ||
this.validator = validator; | ||
} | ||
if (!p.inputHint) { | ||
p.inputHint = botbuilder_1.InputHints.ExpectingInput; | ||
dialogBegin(dc, options) { | ||
// Persist options | ||
const instance = dc.instance; | ||
instance.state = options || {}; | ||
// Send initial prompt | ||
return this.onPrompt(dc, instance.state, false); | ||
} | ||
return p; | ||
dialogContinue(dc) { | ||
// Recognize value | ||
const instance = dc.instance; | ||
return this.onRecognize(dc, instance.state) | ||
.then((recognized) => { | ||
if (this.validator) { | ||
// Call validator | ||
return Promise.resolve(this.validator(dc, recognized)); | ||
} | ||
else { | ||
// Pass through recognized value | ||
return recognized; | ||
} | ||
}).then((output) => { | ||
if (output !== undefined) { | ||
// Return recognized value | ||
return dc.end(output); | ||
} | ||
else if (!dc.context.responded) { | ||
// Send retry prompt | ||
return this.onPrompt(dc, instance.state, true); | ||
} | ||
}); | ||
} | ||
} | ||
exports.formatPrompt = formatPrompt; | ||
exports.Prompt = Prompt; | ||
//# sourceMappingURL=prompt.js.map |
@@ -8,5 +8,5 @@ /** | ||
*/ | ||
import { Dialog } from '../dialog'; | ||
import { DialogSet } from '../dialogSet'; | ||
import { PromptOptions, PromptValidator } from './prompt'; | ||
import { BotContext } from 'botbuilder'; | ||
import { DialogContext } from '../dialogContext'; | ||
import { Prompt, PromptOptions, PromptValidator } from './prompt'; | ||
/** | ||
@@ -26,8 +26,8 @@ * Prompts a user to enter some text. By default the prompt will return to the calling | ||
* dialogs.add('textDemo', [ | ||
* function (context) { | ||
* return dialogs.prompt(context, 'textPrompt', `text: enter some text`); | ||
* function (dc) { | ||
* return dc.prompt('textPrompt', `text: enter some text`); | ||
* }, | ||
* function (context, value) { | ||
* context.reply(`Recognized value: ${value}`); | ||
* return dialogs.end(context); | ||
* function (dc, value) { | ||
* dc.batch.reply(`Recognized value: ${value}`); | ||
* return dc.end(); | ||
* } | ||
@@ -37,4 +37,4 @@ * ]); | ||
*/ | ||
export declare class TextPrompt implements Dialog { | ||
private validator; | ||
export declare class TextPrompt<C extends BotContext> extends Prompt<C, string> { | ||
private prompt; | ||
/** | ||
@@ -46,16 +46,16 @@ * Creates a new instance of the prompt. | ||
* ```JavaScript | ||
* dialogs.add('titlePrompt', new TextPrompt((context, value) => { | ||
* if (value.length < 3) { | ||
* context.reply(`Title should be at least 3 characters long.`); | ||
* return Promise.resolve(); | ||
* dialogs.add('titlePrompt', new TextPrompt((dc, value) => { | ||
* if (!value || value.length < 3) { | ||
* dc.batch.reply(`Title should be at least 3 characters long.`); | ||
* return undefined; | ||
* } else { | ||
* return dialogs.end(context, value.trim()); | ||
* return value.trim(); | ||
* } | ||
* })); | ||
* ``` | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. If the validator replies with a message no additional retry prompt will be sent. | ||
*/ | ||
constructor(validator?: PromptValidator<string> | undefined); | ||
begin(context: BotContext, dialogs: DialogSet, options: PromptOptions): Promise<void>; | ||
continue(context: BotContext, dialogs: DialogSet): Promise<void>; | ||
constructor(validator?: PromptValidator<C, string>); | ||
protected onPrompt(dc: DialogContext<C>, options: PromptOptions, isRetry: boolean): Promise<void>; | ||
protected onRecognize(dc: DialogContext<C>, options: PromptOptions): Promise<string | undefined>; | ||
} |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const prompt_1 = require("./prompt"); | ||
const prompts = require("botbuilder-prompts"); | ||
/** | ||
@@ -18,8 +19,8 @@ * Prompts a user to enter some text. By default the prompt will return to the calling | ||
* dialogs.add('textDemo', [ | ||
* function (context) { | ||
* return dialogs.prompt(context, 'textPrompt', `text: enter some text`); | ||
* function (dc) { | ||
* return dc.prompt('textPrompt', `text: enter some text`); | ||
* }, | ||
* function (context, value) { | ||
* context.reply(`Recognized value: ${value}`); | ||
* return dialogs.end(context); | ||
* function (dc, value) { | ||
* dc.batch.reply(`Recognized value: ${value}`); | ||
* return dc.end(); | ||
* } | ||
@@ -29,3 +30,3 @@ * ]); | ||
*/ | ||
class TextPrompt { | ||
class TextPrompt extends prompt_1.Prompt { | ||
/** | ||
@@ -37,36 +38,28 @@ * Creates a new instance of the prompt. | ||
* ```JavaScript | ||
* dialogs.add('titlePrompt', new TextPrompt((context, value) => { | ||
* if (value.length < 3) { | ||
* context.reply(`Title should be at least 3 characters long.`); | ||
* return Promise.resolve(); | ||
* dialogs.add('titlePrompt', new TextPrompt((dc, value) => { | ||
* if (!value || value.length < 3) { | ||
* dc.batch.reply(`Title should be at least 3 characters long.`); | ||
* return undefined; | ||
* } else { | ||
* return dialogs.end(context, value.trim()); | ||
* return value.trim(); | ||
* } | ||
* })); | ||
* ``` | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. If the validator replies with a message no additional retry prompt will be sent. | ||
*/ | ||
constructor(validator) { | ||
this.validator = validator; | ||
super(validator); | ||
this.prompt = prompts.createTextPrompt(); | ||
} | ||
begin(context, dialogs, options) { | ||
// Persist options | ||
const instance = dialogs.getInstance(context); | ||
instance.state = options || {}; | ||
// Send initial prompt | ||
if (instance.state.prompt) { | ||
context.reply(prompt_1.formatPrompt(instance.state.prompt, instance.state.speak)); | ||
onPrompt(dc, options, isRetry) { | ||
if (isRetry && options.retryPrompt) { | ||
return this.prompt.prompt(dc.context, options.retryPrompt, options.retrySpeak); | ||
} | ||
else if (options.prompt) { | ||
return this.prompt.prompt(dc.context, options.prompt, options.speak); | ||
} | ||
return Promise.resolve(); | ||
} | ||
continue(context, dialogs) { | ||
// Recognize value and call validator | ||
const utterance = context.request && context.request.text ? context.request.text : ''; | ||
if (this.validator) { | ||
return Promise.resolve(this.validator(context, utterance, dialogs)); | ||
} | ||
else { | ||
// Default behavior is to just return recognized value | ||
return dialogs.end(context, utterance); | ||
} | ||
onRecognize(dc, options) { | ||
return this.prompt.recognize(dc.context); | ||
} | ||
@@ -73,0 +66,0 @@ } |
@@ -8,5 +8,5 @@ /** | ||
*/ | ||
import { Promiseable } from 'botbuilder'; | ||
import { Promiseable, BotContext } from 'botbuilder'; | ||
import { Dialog } from './dialog'; | ||
import { DialogSet } from './dialogSet'; | ||
import { DialogContext } from './dialogContext'; | ||
/** | ||
@@ -51,3 +51,3 @@ * Function signature of a waterfall step. | ||
*/ | ||
export declare type WaterfallStep = (context: BotContext, args?: any, next?: SkipStepFunction) => Promiseable<void>; | ||
export declare type WaterfallStep<C extends BotContext> = (dc: DialogContext<C>, args?: any, next?: SkipStepFunction) => Promiseable<any>; | ||
/** | ||
@@ -57,20 +57,72 @@ * When called, control will skip to the next waterfall step. | ||
*/ | ||
export declare type SkipStepFunction = (args?: any) => Promise<void>; | ||
export declare type SkipStepFunction = (args?: any) => Promise<any>; | ||
/** | ||
* Dialog optimized for prompting a user with a series of questions. Waterfalls accept a stack of | ||
* functions which will be executed in sequence. Each waterfall step can ask a question of the user | ||
* by calling either a prompt or another dialog. When the called dialog completes control will be | ||
* returned to the next step of the waterfall and any input collected by the prompt or other dialog | ||
* will be passed to the step as an argument. | ||
* and the users response will be passed as an argument to the next waterfall step. | ||
* | ||
* When a step is executed it should call either `context.begin()`, `context.end()`, | ||
* `context.replace()`, `context.cancelDialog()`, or a prompt. Failing to do so will result | ||
* in the dialog automatically ending the next time the user replies. | ||
* For simple text questions you can send the user a message and then process their answer in the | ||
* next step: | ||
* | ||
* Similarly, calling a dialog/prompt from within the last step of the waterfall will result in | ||
* the waterfall automatically ending once the dialog/prompt completes. This is often desired | ||
* though as the result from tha called dialog/prompt will be passed to the waterfalls parent | ||
* dialog. | ||
* ```JS | ||
* dialogs.add('namePrompt', [ | ||
* async function (dc) { | ||
* dc.instance.state = { first: '', last: '', full: '' }; | ||
* await dc.context.sendActivity(`What's your first name?`); | ||
* }, | ||
* async function (dc, firstName) { | ||
* dc.instance.state.first = firstName; | ||
* await dc.context.sendActivity(`Great ${firstName}! What's your last name?`); | ||
* }, | ||
* async function (dc, lastName) { | ||
* const name = dc.instance.state; | ||
* name.last = lastName; | ||
* name.full = name.first + ' ' + name.last; | ||
* await dc.end(name); | ||
* } | ||
* ]); | ||
* ``` | ||
* | ||
* For more complex sequences you can call other dialogs from within a step and the result returned | ||
* by the dialog will be passed to the next step: | ||
* | ||
* ```JS | ||
* dialogs.add('survey', [ | ||
* async function (dc) { | ||
* dc.instance.state = { name: undefined, languages: '', years: 0 }; | ||
* await dc.begin('namePrompt'); | ||
* }, | ||
* async function (dc, name) { | ||
* dc.instance.state.name = name; | ||
* await dc.context.sendActivity(`Ok ${name.full}... What programming languages do you know?`); | ||
* }, | ||
* async function (dc, languages) { | ||
* dc.instance.state.languages = languages; | ||
* await dc.prompt('yearsPrompt', `Great. So how many years have you been programming?`); | ||
* }, | ||
* async function (dc, years) { | ||
* dc.instance.state.years = years; | ||
* await dc.context.sendActivity(`Thank you for taking our survey.`); | ||
* await dc.end(dc.instance.state); | ||
* } | ||
* ]); | ||
* | ||
* dialogs.add('yearsPrompt', new NumberPrompt(async (dc, value) => { | ||
* if (value === undefined || value < 0 || value > 110) { | ||
* await dc.context.sendActivity(`Enter a number from 0 to 110.`); | ||
* } else { | ||
* return value; | ||
* } | ||
* })); | ||
* ``` | ||
* | ||
* The example builds on the previous `namePrompt` sample and shows how you can call another dialog | ||
* which will ask its own sequence of questions. The dialogs library provides a built-in set of | ||
* prompt classes which can be used to recognize things like dates and numbers in the users response. | ||
* | ||
* You should generally call `dc.end()` or `dc.replace()` from your last waterfall step but if you fail | ||
* to do that the dialog will be automatically ended for you on the users next reply. The users | ||
* response will be passed to the calling dialogs next waterfall step if there is one. | ||
*/ | ||
export declare class Waterfall implements Dialog { | ||
export declare class Waterfall<C extends BotContext> implements Dialog<C> { | ||
private readonly steps; | ||
@@ -81,6 +133,7 @@ /** | ||
*/ | ||
constructor(steps: WaterfallStep[]); | ||
begin(context: BotContext, dialogs: DialogSet, args?: any): Promiseable<void>; | ||
resume(context: BotContext, dialogs: DialogSet, result?: any): Promiseable<void>; | ||
private runStep(context, dialogs, result?); | ||
constructor(steps: WaterfallStep<C>[]); | ||
dialogBegin(dc: DialogContext<C>, args?: any): Promiseable<any>; | ||
dialogContinue(dc: DialogContext<C>): Promise<any>; | ||
dialogResume(dc: DialogContext<C>, result?: any): Promiseable<any>; | ||
private runStep(dc, result?); | ||
} |
@@ -6,14 +6,66 @@ "use strict"; | ||
* functions which will be executed in sequence. Each waterfall step can ask a question of the user | ||
* by calling either a prompt or another dialog. When the called dialog completes control will be | ||
* returned to the next step of the waterfall and any input collected by the prompt or other dialog | ||
* will be passed to the step as an argument. | ||
* and the users response will be passed as an argument to the next waterfall step. | ||
* | ||
* When a step is executed it should call either `context.begin()`, `context.end()`, | ||
* `context.replace()`, `context.cancelDialog()`, or a prompt. Failing to do so will result | ||
* in the dialog automatically ending the next time the user replies. | ||
* For simple text questions you can send the user a message and then process their answer in the | ||
* next step: | ||
* | ||
* Similarly, calling a dialog/prompt from within the last step of the waterfall will result in | ||
* the waterfall automatically ending once the dialog/prompt completes. This is often desired | ||
* though as the result from tha called dialog/prompt will be passed to the waterfalls parent | ||
* dialog. | ||
* ```JS | ||
* dialogs.add('namePrompt', [ | ||
* async function (dc) { | ||
* dc.instance.state = { first: '', last: '', full: '' }; | ||
* await dc.context.sendActivity(`What's your first name?`); | ||
* }, | ||
* async function (dc, firstName) { | ||
* dc.instance.state.first = firstName; | ||
* await dc.context.sendActivity(`Great ${firstName}! What's your last name?`); | ||
* }, | ||
* async function (dc, lastName) { | ||
* const name = dc.instance.state; | ||
* name.last = lastName; | ||
* name.full = name.first + ' ' + name.last; | ||
* await dc.end(name); | ||
* } | ||
* ]); | ||
* ``` | ||
* | ||
* For more complex sequences you can call other dialogs from within a step and the result returned | ||
* by the dialog will be passed to the next step: | ||
* | ||
* ```JS | ||
* dialogs.add('survey', [ | ||
* async function (dc) { | ||
* dc.instance.state = { name: undefined, languages: '', years: 0 }; | ||
* await dc.begin('namePrompt'); | ||
* }, | ||
* async function (dc, name) { | ||
* dc.instance.state.name = name; | ||
* await dc.context.sendActivity(`Ok ${name.full}... What programming languages do you know?`); | ||
* }, | ||
* async function (dc, languages) { | ||
* dc.instance.state.languages = languages; | ||
* await dc.prompt('yearsPrompt', `Great. So how many years have you been programming?`); | ||
* }, | ||
* async function (dc, years) { | ||
* dc.instance.state.years = years; | ||
* await dc.context.sendActivity(`Thank you for taking our survey.`); | ||
* await dc.end(dc.instance.state); | ||
* } | ||
* ]); | ||
* | ||
* dialogs.add('yearsPrompt', new NumberPrompt(async (dc, value) => { | ||
* if (value === undefined || value < 0 || value > 110) { | ||
* await dc.context.sendActivity(`Enter a number from 0 to 110.`); | ||
* } else { | ||
* return value; | ||
* } | ||
* })); | ||
* ``` | ||
* | ||
* The example builds on the previous `namePrompt` sample and shows how you can call another dialog | ||
* which will ask its own sequence of questions. The dialogs library provides a built-in set of | ||
* prompt classes which can be used to recognize things like dates and numbers in the users response. | ||
* | ||
* You should generally call `dc.end()` or `dc.replace()` from your last waterfall step but if you fail | ||
* to do that the dialog will be automatically ended for you on the users next reply. The users | ||
* response will be passed to the calling dialogs next waterfall step if there is one. | ||
*/ | ||
@@ -28,22 +80,27 @@ class Waterfall { | ||
} | ||
begin(context, dialogs, args) { | ||
const instance = dialogs.getInstance(context); | ||
dialogBegin(dc, args) { | ||
const instance = dc.instance; | ||
instance.step = 0; | ||
return this.runStep(context, dialogs, args); | ||
return this.runStep(dc, args); | ||
} | ||
resume(context, dialogs, result) { | ||
const instance = dialogs.getInstance(context); | ||
dialogContinue(dc) { | ||
const instance = dc.instance; | ||
instance.step += 1; | ||
return this.runStep(context, dialogs, result); | ||
return this.runStep(dc, dc.context.request.text || ''); | ||
} | ||
runStep(context, dialogs, result) { | ||
dialogResume(dc, result) { | ||
const instance = dc.instance; | ||
instance.step += 1; | ||
return this.runStep(dc, result); | ||
} | ||
runStep(dc, result) { | ||
try { | ||
const instance = dialogs.getInstance(context); | ||
const instance = dc.instance; | ||
const step = instance.step; | ||
if (step >= 0 && step < this.steps.length) { | ||
// Execute step | ||
return Promise.resolve(this.steps[step](context, result, (r) => { | ||
return Promise.resolve(this.steps[step](dc, result, (r) => { | ||
// Skip to next step | ||
instance.step += 1; | ||
return this.runStep(context, r); | ||
return this.runStep(dc, r); | ||
})); | ||
@@ -53,3 +110,3 @@ } | ||
// End of waterfall so just return to parent | ||
return dialogs.end(context); | ||
return dc.end(result); | ||
} | ||
@@ -56,0 +113,0 @@ } |
@@ -5,3 +5,3 @@ { | ||
"description": "A dialog stack based conversation manager for Microsoft Bot Builder.", | ||
"version": "4.0.0-m1.11", | ||
"version": "4.0.0-m2.1", | ||
"license": "MIT", | ||
@@ -24,4 +24,4 @@ "keywords": [ | ||
"dependencies": { | ||
"botbuilder": "4.0.0-m1.10", | ||
"botbuilder-choices": "4.0.0-m1.10", | ||
"botbuilder": "4.0.0-m2.1", | ||
"botbuilder-prompts": "4.0.0-m2.1", | ||
"@microsoft/recognizers-text-suite": "^1.0.0" | ||
@@ -28,0 +28,0 @@ }, |
@@ -8,4 +8,4 @@ /** | ||
*/ | ||
import { Promiseable } from 'botbuilder'; | ||
import { DialogSet } from './dialogSet'; | ||
import { BotContext, Promiseable } from 'botbuilder'; | ||
import { DialogContext } from './dialogContext'; | ||
@@ -17,10 +17,9 @@ /** | ||
*/ | ||
export interface Dialog { | ||
export interface Dialog<C extends BotContext> { | ||
/** | ||
* Method called when a new dialog has been pushed onto the stack and is being activated. | ||
* @param context The dialog context for the current turn of conversation. | ||
* @param dialogs The dialogs parent set. | ||
* @param args (Optional) arguments that were passed to the dialog during `begin()` call that started the instance. | ||
* @param dc The dialog context for the current turn of conversation. | ||
* @param dialogArgs (Optional) arguments that were passed to the dialog during `begin()` call that started the instance. | ||
*/ | ||
begin(context: BotContext, dialogs: DialogSet, args?: any): Promiseable<void>; | ||
dialogBegin(dc: DialogContext<C>, dialogArgs?: any): Promiseable<any>; | ||
@@ -34,6 +33,5 @@ /** | ||
* replies. | ||
* @param context The dialog context for the current turn of conversation. | ||
* @param dialogs The dialogs parent set. | ||
* @param dc The dialog context for the current turn of conversation. | ||
*/ | ||
continue?(context: BotContext, dialogs: DialogSet): Promiseable<void>; | ||
dialogContinue?(dc: DialogContext<C>): Promiseable<any>; | ||
@@ -47,7 +45,6 @@ /** | ||
* to the current dialogs parent. | ||
* @param context The dialog context for the current turn of conversation. | ||
* @param dialogs The dialogs parent set. | ||
* @param dc The dialog context for the current turn of conversation. | ||
* @param result (Optional) value returned from the dialog that was called. The type of the value returned is dependant on the dialog that was called. | ||
*/ | ||
resume?(context: BotContext, dialogs: DialogSet, result?: any): Promiseable<void>; | ||
dialogResume?(dc: DialogContext<C>, result?: any): Promiseable<any>; | ||
} | ||
@@ -58,3 +55,3 @@ | ||
*/ | ||
export interface DialogInstance<T extends Object> { | ||
export interface DialogInstance<T extends any = any> { | ||
/** ID of the dialog this instance is for. */ | ||
@@ -61,0 +58,0 @@ id: string; |
@@ -8,7 +8,6 @@ /** | ||
*/ | ||
import { Activity } from 'botbuilder'; | ||
import { BotContext, BotState, StoreItem, Activity } from 'botbuilder'; | ||
import { Dialog, DialogInstance } from './dialog'; | ||
import { Waterfall, WaterfallStep } from './waterfall'; | ||
import { PromptOptions, ChoicePromptOptions } from './prompts/index'; | ||
import { Choice } from 'botbuilder-choices'; | ||
import { DialogContext } from './dialogContext'; | ||
@@ -48,24 +47,6 @@ /** | ||
*/ | ||
export class DialogSet { | ||
private readonly stackName: string; | ||
private readonly dialogs: { [id:string]: Dialog; } = {}; | ||
export class DialogSet<C extends BotContext = BotContext> { | ||
private readonly dialogs: { [id:string]: Dialog<C>; } = {}; | ||
/** | ||
* Creates an empty dialog set. The ability to name the sets dialog stack means that multiple | ||
* stacks can coexist within the same bot. Middleware can use their own private set of | ||
* dialogs without fear of colliding with the bots dialog stack. | ||
* | ||
* **Example usage:** | ||
* | ||
* ```JavaScript | ||
* const dialogs = new DialogSet('myPrivateStack'); | ||
* ``` | ||
* @param stackName (Optional) name of the field to store the dialog stack in off the bots conversation state object. This defaults to 'dialogStack'. | ||
*/ | ||
constructor (stackName?: string) { | ||
this.stackName = stackName || 'dialogStack'; | ||
} | ||
/** | ||
* Adds a new dialog to the set and returns the added dialog. | ||
@@ -87,183 +68,15 @@ * | ||
*/ | ||
public add<T extends Dialog>(dialogId: string, dialogOrSteps: T): T; | ||
public add(dialogId: string, dialogOrSteps: WaterfallStep[]): Waterfall; | ||
public add(dialogId: string, dialogOrSteps: Dialog|WaterfallStep[]): Dialog { | ||
public add(dialogId: string, dialogOrSteps: Dialog<C>): Dialog<C>; | ||
public add(dialogId: string, dialogOrSteps: WaterfallStep<C>[]): Waterfall<C>; | ||
public add(dialogId: string, dialogOrSteps: Dialog<C>|WaterfallStep<C>[]): Dialog<C> { | ||
if (this.dialogs.hasOwnProperty(dialogId)) { throw new Error(`DialogSet.add(): A dialog with an id of '${dialogId}' already added.`) } | ||
return this.dialogs[dialogId] = Array.isArray(dialogOrSteps) ? new Waterfall(dialogOrSteps as any) : dialogOrSteps; | ||
} | ||
/** | ||
* Pushes a new dialog onto the dialog stack. | ||
* | ||
* **Example usage:** | ||
* | ||
* ```JavaScript | ||
* return dialogs.begin(context, 'greeting', user); | ||
* ``` | ||
* @param context Context object for the current turn of conversation with the user. | ||
* @param dialogId ID of the dialog to start. | ||
* @param dialogArgs (Optional) additional argument(s) to pass to the dialog being started. | ||
*/ | ||
public begin(context: BotContext, dialogId: string, dialogArgs?: any): Promise<void> { | ||
try { | ||
// Lookup dialog | ||
const dialog = this.find(dialogId); | ||
if (!dialog) { throw new Error(`DialogSet.begin(): A dialog with an id of '${dialogId}' wasn't found.`) } | ||
// Push new instance onto stack. | ||
const instance: DialogInstance<any> = { | ||
id: dialogId, | ||
state: {} | ||
}; | ||
this.getStack(context).push(instance); | ||
// Call dialogs begin() method. | ||
return Promise.resolve(dialog.begin(context, this, dialogArgs)); | ||
} catch(err) { | ||
return Promise.reject(err); | ||
} | ||
} | ||
/** | ||
* Helper function to simplify formatting the options for calling a prompt dialog. This helper will | ||
* construct a `PromptOptions` structure and then call [begin(context, dialogId, options)](#begin). | ||
* | ||
* **Example usage:** | ||
* | ||
* ```JavaScript | ||
* return dialogs.prompt(context, 'confirmPrompt', `Are you sure you'd like to quit?`); | ||
* ``` | ||
* @param O (Optional) type of options expected by the prompt. | ||
* @param context Context object for the current turn of conversation with the user. | ||
* @param dialogId ID of the prompt to start. | ||
* @param prompt Initial prompt to send the user. | ||
* @param choicesOrOptions (Optional) array of choices to prompt the user for or additional prompt options. | ||
*/ | ||
public prompt<O extends PromptOptions = PromptOptions>(context: BotContext, dialogId: string, prompt: string|Partial<Activity>, choicesOrOptions?: O|(string|Choice)[], options?: O): Promise<void> { | ||
const args = Object.assign({}, Array.isArray(choicesOrOptions) ? { choices: choicesOrOptions } : choicesOrOptions) as O; | ||
if (prompt) { args.prompt = prompt } | ||
return this.begin(context, dialogId, args); | ||
public createContext(context: C, state: object): DialogContext<C> { | ||
if (!Array.isArray(state['dialogStack'])) { state['dialogStack'] = [] } | ||
return new DialogContext(this, context, state['dialogStack']); | ||
} | ||
/** | ||
* Continues execution of the active dialog, if there is one, by passing the context object to | ||
* its `Dialog.continue()` method. You can check `context.responded` after the call completes | ||
* to determine if a dialog was run and a reply was sent to the user. | ||
* | ||
* **Example usage:** | ||
* | ||
* ```JavaScript | ||
* return dialogs.continue(context).then(() => { | ||
* if (!dialog.responded) { | ||
* return dialogs.begin(context, 'fallback'); | ||
* } | ||
* }); | ||
* ``` | ||
* @param context Context object for the current turn of conversation with the user. | ||
*/ | ||
public continue(context: BotContext): Promise<void> { | ||
try { | ||
if (this.getStack(context).length > 0) { | ||
// Get current dialog instance | ||
const instance = this.getInstance(context); | ||
// Lookup dialog | ||
const dialog = this.find(instance.id); | ||
if (!dialog) { throw new Error(`DialogSet.continue(): Can't continue dialog. A dialog with an id of '${instance.id}' wasn't found.`) } | ||
// Check for existence of a continue() method | ||
if (dialog.continue) { | ||
// Continue execution of dialog | ||
return Promise.resolve(dialog.continue(context, this)); | ||
} else { | ||
// Just end the dialog | ||
return this.end(context); | ||
} | ||
} else { | ||
return Promise.resolve(); | ||
} | ||
} catch(err) { | ||
return Promise.reject(err); | ||
} | ||
} | ||
/** | ||
* Ends a dialog by popping it off the stack and returns an optional result to the dialogs | ||
* parent. The parent dialog is the dialog the started the on being ended via a call to | ||
* either [begin()](#begin) or [prompt()](#prompt). | ||
* | ||
* The parent dialog will have its `Dialog.resume()` method invoked with any returned | ||
* result. If the parent dialog hasn't implemented a `resume()` method then it will be | ||
* automatically ended as well and the result passed to its parent. If there are no more | ||
* parent dialogs on the stack then processing of the turn will end. | ||
* | ||
* **Example usage:** | ||
* | ||
* ```JavaScript | ||
* dialogs.add('showUptime', [ | ||
* function (context) { | ||
* const elapsed = new Date().getTime() - started; | ||
* context.reply(`I've been running for ${elapsed / 1000} seconds.`); | ||
* return dialogs.end(context, elapsed); | ||
* } | ||
* ]) | ||
* const started = new Date().getTime(); | ||
* ``` | ||
* @param context Context object for the current turn of conversation with the user. | ||
* @param result (Optional) result to pass to the parent dialogs `Dialog.resume()` method. | ||
*/ | ||
public end(context: BotContext, result?: any): Promise<void> { | ||
try { | ||
// Pop current dialog off the stack | ||
const stack = this.getStack(context); | ||
if (stack.length > 0) { stack.pop() } | ||
// Resume previous dialog | ||
if (stack.length > 0) { | ||
// Get dialog instance | ||
const instance = this.getInstance(context); | ||
// Lookup dialog | ||
const dialog = this.find(instance.id); | ||
if (!dialog) { throw new Error(`DialogSet.end(): Can't resume previous dialog. A dialog with an id of '${instance.id}' wasn't found.`) } | ||
// Check for existence of a resumeDialog() method | ||
if (dialog.resume) { | ||
// Return result to previous dialog | ||
return Promise.resolve(dialog.resume(context, this, result)); | ||
} else { | ||
// Just end the dialog | ||
return this.end(context); | ||
} | ||
} else { | ||
return Promise.resolve(); | ||
} | ||
} catch(err) { | ||
return Promise.reject(err); | ||
} | ||
} | ||
/** | ||
* Deletes any existing dialog stack thus cancelling all dialogs on the stack. | ||
* | ||
* **Example usage:** | ||
* | ||
* ```JavaScript | ||
* return dialogs.endAll(context) | ||
* .then(() => dialogs.begin(context, 'addAlarm')); | ||
* ``` | ||
* @param context Context object for the current turn of conversation with the user. | ||
*/ | ||
public endAll(context: BotContext): Promise<void> { | ||
try { | ||
// Cancel any current dialogs | ||
const state = getConversationState(context); | ||
state[this.stackName] = []; | ||
return Promise.resolve(); | ||
} catch (err) { | ||
return Promise.reject(err); | ||
} | ||
} | ||
/** | ||
* Finds a dialog that was previously added to the set using [add()](#add). | ||
@@ -279,81 +92,7 @@ * | ||
*/ | ||
public find<T extends Dialog = Dialog>(dialogId: string): T|undefined { | ||
public find<T extends Dialog<C> = Dialog<C>>(dialogId: string): T|undefined { | ||
return this.dialogs.hasOwnProperty(dialogId) ? this.dialogs[dialogId] as T : undefined; | ||
} | ||
/** | ||
* Returns the dialog stack persisted for a conversation. | ||
* | ||
* **Example usage:** | ||
* | ||
* ```JavaScript | ||
* const hasActiveDialog = dialogs.getStack(context).length > 0; | ||
* ``` | ||
* @param context Context object for the current turn of conversation with the user. | ||
*/ | ||
public getStack(context: BotContext): DialogInstance<any>[] { | ||
const state = getConversationState(context); | ||
if (!Array.isArray(state[this.stackName])) { state[this.stackName] = [] } | ||
return state[this.stackName]; | ||
} | ||
/** | ||
* Returns the active dialog instance on the top of the stack. Throws an error if the stack is | ||
* empty so use `dialogs.getStack(context).length > 0` to protect calls where the stack could | ||
* be empty. | ||
* | ||
* **Example usage:** | ||
* | ||
* ```JavaScript | ||
* const dialogState = dialogs.getInstance(context).state; | ||
* ``` | ||
* @param T (Optional) type of dialog state being persisted by the instance. | ||
* @param context Context object for the current turn of conversation with the user. | ||
*/ | ||
public getInstance<T extends Object = { [key:string]: any; }>(context: BotContext): DialogInstance<T> { | ||
const stack = this.getStack(context); | ||
if (stack.length < 1) { throw new Error(`DialogSet.getInstance(): No active dialog on the stack.`) } | ||
return stack[stack.length - 1]; | ||
} | ||
/** | ||
* Ends the current dialog and starts a new dialog in its place. This is particularly useful | ||
* for creating loops or redirecting to another dialog. | ||
* | ||
* **Example usage:** | ||
* | ||
* ```JavaScript | ||
* dialogs.add('loop', [ | ||
* function (context, args) { | ||
* dialogs.getInstance(context).state = args; | ||
* return dialogs.begin(context, args.dialogId); | ||
* }, | ||
* function (context) { | ||
* const args = dialogs.getInstance(context).state; | ||
* return dialogs.replace(context, 'loop', args); | ||
* } | ||
* ]); | ||
* ``` | ||
* @param context Context object for the current turn of conversation with the user. | ||
* @param dialogId ID of the new dialog to start. | ||
* @param dialogArgs (Optional) additional argument(s) to pass to the new dialog. | ||
*/ | ||
public replace(context: BotContext, dialogId: string, dialogArgs?: any): Promise<void> { | ||
try { | ||
// Pop stack | ||
const stack = this.getStack(context); | ||
if (stack.length > 0) { stack.pop() } | ||
// Start replacement dialog | ||
return this.begin(context, dialogId, dialogArgs); | ||
} catch (err) { | ||
return Promise.reject(err); | ||
} | ||
} | ||
} | ||
function getConversationState(context: BotContext): ConversationState { | ||
if (!context.state.conversation) { throw new Error(`DialogSet: No conversation state found. Please add a BotStateManager instance to your bots middleware stack.`) } | ||
return context.state.conversation; | ||
} | ||
@@ -6,3 +6,6 @@ /** | ||
export * from './prompts/index'; | ||
export * from './compositeControl'; | ||
export * from './control'; | ||
export * from './dialog'; | ||
export * from './dialogContext'; | ||
export * from './dialogSet'; | ||
@@ -13,2 +16,2 @@ export * from './waterfall'; | ||
// import interfaces from two libraries when working with dialogs. | ||
export { FoundChoice, Choice, ChoiceStylerOptions } from 'botbuilder-choices'; | ||
export { FoundChoice, Choice, ChoiceFactoryOptions, FoundDatetime, FindChoicesOptions, ListStyle } from 'botbuilder-prompts'; |
@@ -8,6 +8,6 @@ /** | ||
*/ | ||
import { Attachment } from 'botbuilder'; | ||
import { Dialog } from '../dialog'; | ||
import { DialogSet } from '../dialogSet'; | ||
import { PromptOptions, PromptValidator, formatPrompt } from './prompt'; | ||
import { BotContext, Attachment } from 'botbuilder'; | ||
import { DialogContext } from '../dialogContext'; | ||
import { Prompt, PromptOptions, PromptValidator } from './prompt'; | ||
import * as prompts from 'botbuilder-prompts'; | ||
@@ -28,8 +28,8 @@ /** | ||
* dialogs.add('uploadImage', [ | ||
* function (context) { | ||
* return dialogs.prompt(context, 'attachmentPrompt', `Send me image(s)`); | ||
* function (dc) { | ||
* return dc.prompt('attachmentPrompt', `Send me image(s)`); | ||
* }, | ||
* function (context, attachments) { | ||
* context.reply(`Processing ${attachments.length} images.`); | ||
* return dialogs.end(context); | ||
* function (dc, attachments) { | ||
* dc.batch.reply(`Processing ${attachments.length} images.`); | ||
* return dc.end(); | ||
* } | ||
@@ -39,3 +39,5 @@ * ]); | ||
*/ | ||
export class AttachmentPrompt implements Dialog { | ||
export class AttachmentPrompt<C extends BotContext> extends Prompt<C, Attachment[]> { | ||
private prompt: prompts.AttachmentPrompt; | ||
/** | ||
@@ -47,46 +49,30 @@ * Creates a new instance of the prompt. | ||
* ```JavaScript | ||
* dialogs.add('imagePrompt', new AttachmentPrompt((context, values) => { | ||
* if (values.length < 1) { | ||
* context.reply(`Send me an image or say 'cancel'.`); | ||
* return Prompts.resolve(); | ||
* dialogs.add('imagePrompt', new AttachmentPrompt((dc, values) => { | ||
* if (!Array.isArray(values) || values.length < 1) { | ||
* dc.batch.reply(`Send me an image or say "cancel".`); | ||
* return undefined; | ||
* } else { | ||
* return dialogs.end(context, values); | ||
* return values; | ||
* } | ||
* })); | ||
* ``` | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. If the validator replies with a message no additional retry prompt will be sent. | ||
*/ | ||
constructor(private validator?: PromptValidator<Attachment[]>) {} | ||
constructor(validator?: PromptValidator<C, Attachment[]>) { | ||
super(validator); | ||
this.prompt = prompts.createAttachmentPrompt(); | ||
} | ||
public begin(context: BotContext, dialogs: DialogSet, options: PromptOptions): Promise<void> { | ||
// Persist options | ||
const instance = dialogs.getInstance<PromptOptions>(context); | ||
instance.state = options || {}; | ||
// Send initial prompt | ||
if (instance.state.prompt) { context.reply(formatPrompt(instance.state.prompt, instance.state.speak)) } | ||
protected onPrompt(dc: DialogContext<C>, options: PromptOptions, isRetry: boolean): Promise<void> { | ||
if (isRetry && options.retryPrompt) { | ||
return this.prompt.prompt(dc.context, options.retryPrompt, options.retrySpeak); | ||
} else if (options.prompt) { | ||
return this.prompt.prompt(dc.context, options.prompt, options.speak); | ||
} | ||
return Promise.resolve(); | ||
} | ||
public continue(context: BotContext, dialogs: DialogSet): Promise<void> { | ||
// Recognize value | ||
const options = dialogs.getInstance<PromptOptions>(context).state; | ||
const values: Attachment[] = context.request && context.request.attachments ? context.request.attachments : []; | ||
if (this.validator) { | ||
// Call validator for further processing | ||
return Promise.resolve(this.validator(context, values, dialogs)); | ||
} else if (values.length > 0) { | ||
// Return recognized values | ||
return dialogs.end(context, values); | ||
} else { | ||
if (options.retryPrompt) { | ||
// Send retry prompt to user | ||
context.reply(formatPrompt(options.retryPrompt, options.retrySpeak)); | ||
} else if (options.prompt) { | ||
// Send original prompt to user | ||
context.reply(formatPrompt(options.prompt, options.speak)); | ||
} | ||
return Promise.resolve(); | ||
} | ||
protected onRecognize(dc: DialogContext<C>, options: PromptOptions): Promise<Attachment[]|undefined> { | ||
return this.prompt.recognize(dc.context); | ||
} | ||
} |
@@ -8,43 +8,16 @@ /** | ||
*/ | ||
import { Promiseable, Activity, InputHints } from 'botbuilder'; | ||
import { Dialog } from '../dialog'; | ||
import { DialogSet } from '../dialogSet'; | ||
import { PromptOptions, PromptValidator } from './prompt'; | ||
import { Choice, ChoiceStyler, ChoiceStylerOptions, recognizeChoices, FindChoicesOptions, FoundChoice } from 'botbuilder-choices'; | ||
import { BotContext } from 'botbuilder'; | ||
import { DialogContext } from '../dialogContext'; | ||
import { Prompt, PromptOptions, PromptValidator } from './prompt'; | ||
import * as prompts from 'botbuilder-prompts'; | ||
/** | ||
* Controls the way that choices for a `ChoicePrompt` or yes/no options for a `ConfirmPrompt` are | ||
* presented to a user. | ||
*/ | ||
export enum ListStyle { | ||
/** Don't include any choices for prompt. */ | ||
none, | ||
/** Automatically select the appropriate style for the current channel. */ | ||
auto, | ||
/** Add choices to prompt as an inline list. */ | ||
inline, | ||
/** Add choices to prompt as a numbered list. */ | ||
list, | ||
/** Add choices to prompt as suggested actions. */ | ||
suggestedAction | ||
} | ||
/** Additional options that can be used to configure a `ChoicePrompt`. */ | ||
export interface ChoicePromptOptions extends PromptOptions { | ||
/** List of choices to recognize. */ | ||
choices?: (string|Choice)[]; | ||
/** Preferred style of the choices sent to the user. The default value is `ListStyle.auto`. */ | ||
style?: ListStyle; | ||
choices?: (string|prompts.Choice)[]; | ||
} | ||
/** | ||
* Prompts a user to make a selection from a list of choices. By default the prompt will return to | ||
* the calling dialog a `FoundChoice` for the choice the user selected. This can be overridden | ||
* using a custom `PromptValidator`. | ||
* Prompts a user to confirm something with a yes/no response. By default the prompt will return | ||
* to the calling dialog a `boolean` representing the users selection. | ||
* | ||
@@ -61,8 +34,8 @@ * **Example usage:** | ||
* dialogs.add('choiceDemo', [ | ||
* function (context) { | ||
* return dialogs.prompt(context, 'choicePrompt', `choice: select a color`, ['red', 'green', 'blue']); | ||
* function (dc) { | ||
* return dc.prompt('choicePrompt', `choice: select a color`, ['red', 'green', 'blue']); | ||
* }, | ||
* function (context, choice: FoundChoice) { | ||
* context.reply(`Recognized choice: ${JSON.stringify(choice)}`); | ||
* return dialogs.end(context); | ||
* function (dc, choice) { | ||
* dc.batch.reply(`Recognized choice: ${JSON.stringify(choice)}`); | ||
* return dc.end(); | ||
* } | ||
@@ -72,9 +45,5 @@ * ]); | ||
*/ | ||
export class ChoicePrompt implements Dialog { | ||
/** Additional options passed to the `ChoiceStyler` and used to tweak the style of choices rendered to the user. */ | ||
public readonly stylerOptions: ChoiceStylerOptions = {}; | ||
export class ChoicePrompt<C extends BotContext> extends Prompt<C, prompts.FoundChoice> { | ||
private prompt: prompts.ChoicePrompt; | ||
/** Additional options passed to the `recognizeChoices()` function. */ | ||
public readonly recognizerOptions: FindChoicesOptions = {}; | ||
/** | ||
@@ -88,88 +57,51 @@ * Creates a new instance of the prompt. | ||
* ``` | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. If the validator replies with a message no additional retry prompt will be sent. | ||
* @param defaultLocale (Optional) locale to use if `dc.context.request.locale` not specified. Defaults to a value of `en-us`. | ||
*/ | ||
constructor(private validator?: PromptValidator<FoundChoice|undefined>) {} | ||
constructor(validator?: PromptValidator<C, prompts.FoundChoice>, defaultLocale?: string) { | ||
super(validator); | ||
this.prompt = prompts.createChoicePrompt(undefined, defaultLocale); | ||
} | ||
public begin(context: BotContext, dialogs: DialogSet, options: ChoicePromptOptions): Promise<void> { | ||
// Persist options | ||
const instance = dialogs.getInstance<ChoicePromptOptions>(context); | ||
instance.state = options || {}; | ||
/** | ||
* Sets additional options passed to the `ChoiceFactory` and used to tweak the style of choices | ||
* rendered to the user. | ||
* @param options Additional options to set. | ||
*/ | ||
public choiceOptions(options: prompts.ChoiceFactoryOptions): this { | ||
Object.assign(this.prompt.choiceOptions, options); | ||
return this; | ||
} | ||
// Send initial prompt | ||
if (instance.state.prompt) { | ||
return this.sendChoicePrompt(context, dialogs, instance.state.prompt, instance.state.speak); | ||
} else { | ||
return Promise.resolve(); | ||
} | ||
/** | ||
* Sets additional options passed to the `recognizeChoices()` function. | ||
* @param options Additional options to set. | ||
*/ | ||
public recognizerOptions(options: prompts.FindChoicesOptions): this { | ||
Object.assign(this.prompt.recognizerOptions, options); | ||
return this; | ||
} | ||
public continue(context: BotContext, dialogs: DialogSet): Promise<void> { | ||
// Recognize value | ||
const options = dialogs.getInstance<ChoicePromptOptions>(context).state; | ||
const utterance = context.request && context.request.text ? context.request.text : ''; | ||
const results = recognizeChoices(utterance, options.choices || [], this.recognizerOptions); | ||
const value = results.length > 0 ? results[0].resolution : undefined; | ||
if (this.validator) { | ||
// Call validator for further processing | ||
return Promise.resolve(this.validator(context, value, dialogs)); | ||
} else if (value) { | ||
// Return recognized choice | ||
return dialogs.end(context, value); | ||
} else if (options.retryPrompt) { | ||
// Send retry prompt to user | ||
return this.sendChoicePrompt(context, dialogs, options.retryPrompt, options.retrySpeak); | ||
} else if (options.prompt) { | ||
// Send original prompt to user | ||
return this.sendChoicePrompt(context, dialogs, options.prompt, options.speak); | ||
} else { | ||
return Promise.resolve(); | ||
} | ||
/** | ||
* Sets the style of the choice list rendered to the user when prompting. | ||
* @param listStyle Type of list to render to to user. Defaults to `ListStyle.auto`. | ||
*/ | ||
public style(listStyle: prompts.ListStyle): this { | ||
this.prompt.style = listStyle; | ||
return this; | ||
} | ||
private sendChoicePrompt(context: BotContext, dialogs: DialogSet, prompt: string|Partial<Activity>, speak?: string): Promise<void> { | ||
if (typeof prompt === 'string') { | ||
const options = dialogs.getInstance<ChoicePromptOptions>(context).state; | ||
context.reply(formatChoicePrompt(context, options.choices || [], prompt, speak, this.stylerOptions, options.style)); | ||
} else { | ||
context.reply(prompt); | ||
protected onPrompt(dc: DialogContext<C>, options: ChoicePromptOptions, isRetry: boolean): Promise<void> { | ||
const choices = options.choices || []; | ||
if (isRetry && options.retryPrompt) { | ||
return this.prompt.prompt(dc.context, choices, options.retryPrompt, options.retrySpeak); | ||
} else if (choices.length || options.prompt) { | ||
return this.prompt.prompt(dc.context, choices, options.prompt, options.speak); | ||
} | ||
return Promise.resolve(); | ||
return Promise.resolve(); | ||
} | ||
} | ||
/** | ||
* Helper function to format a choice prompt for a given `ListStyle`. An activity will be returned | ||
* that can then be sent to the user. | ||
* | ||
* **Example usage:** | ||
* | ||
* ```JavaScript | ||
* const { formatChoicePrompt } = require('botbuilder-dialogs'); | ||
* | ||
* context.reply(formatChoicePrompt(context, ['red', 'green', 'blue'], `Select a color`)); | ||
* ``` | ||
* @param channelOrContext Context for the current turn of conversation with the user or the ID of a channel. This is used when `style == ListStyle.auto`. | ||
* @param choices Array of choices being prompted for. | ||
* @param text (Optional) prompt text to show the user along with the options. | ||
* @param speak (Optional) SSML to speak to the user on channels like Cortana. The messages `inputHint` will be automatically set to `InputHints.expectingInput`. | ||
* @param options (Optional) additional choice styler options used to customize the rendering of the prompts choice list. | ||
* @param style (Optional) list style to use when rendering prompt. Defaults to `ListStyle.auto`. | ||
*/ | ||
export function formatChoicePrompt(channelOrContext: string|BotContext, choices: (string|Choice)[], text?: string, speak?: string, options?: ChoiceStylerOptions, style?: ListStyle): Partial<Activity> { | ||
switch (style) { | ||
case ListStyle.auto: | ||
default: | ||
return ChoiceStyler.forChannel(channelOrContext, choices, text, speak, options); | ||
case ListStyle.inline: | ||
return ChoiceStyler.inline(choices, text, speak, options); | ||
case ListStyle.list: | ||
return ChoiceStyler.list(choices, text, speak, options); | ||
case ListStyle.suggestedAction: | ||
return ChoiceStyler.suggestedAction(choices, text, speak, options); | ||
case ListStyle.none: | ||
const p = { type: 'message', text: text || '' } as Partial<Activity>; | ||
if (speak) { p.speak = speak } | ||
if (!p.inputHint) { p.inputHint = InputHints.ExpectingInput } | ||
return p; | ||
protected onRecognize(dc: DialogContext<C>, options: ChoicePromptOptions): Promise<prompts.FoundChoice|undefined> { | ||
return this.prompt.recognize(dc.context, options.choices || []); | ||
} | ||
} |
@@ -8,22 +8,7 @@ /** | ||
*/ | ||
import { Activity } from 'botbuilder'; | ||
import { Dialog } from '../dialog'; | ||
import { DialogSet } from '../dialogSet'; | ||
import { PromptOptions, PromptValidator } from './prompt'; | ||
import { ListStyle, formatChoicePrompt } from './choicePrompt'; | ||
import { ChoiceStylerOptions, Choice } from 'botbuilder-choices'; | ||
import * as Recognizers from '@microsoft/recognizers-text-choice'; | ||
import { BotContext } from 'botbuilder'; | ||
import { DialogContext } from '../dialogContext'; | ||
import { Prompt, PromptOptions, PromptValidator } from './prompt'; | ||
import * as prompts from 'botbuilder-prompts'; | ||
/** Map of `ConfirmPrompt` choices for each locale the bot supports. */ | ||
export interface ConfirmChoices { | ||
[locale:string]: (string|Choice)[]; | ||
} | ||
/** Additional options that can be used to configure a `ChoicePrompt`. */ | ||
export interface ConfirmPromptOptions extends PromptOptions { | ||
/** Preferred style of the yes/no choices sent to the user. The default value is `ListStyle.auto`. */ | ||
style?: ListStyle; | ||
} | ||
/** | ||
@@ -43,8 +28,8 @@ * Prompts a user to confirm something with a yes/no response. By default the prompt will return | ||
* dialogs.add('confirmDemo', [ | ||
* function (context) { | ||
* return dialogs.prompt(context, 'confirmPrompt', `confirm: answer "yes" or "no"`); | ||
* function (dc) { | ||
* return dc.prompt('confirmPrompt', `confirm: answer "yes" or "no"`); | ||
* }, | ||
* function (context, value) { | ||
* context.reply(`Recognized value: ${value}`); | ||
* return dialogs.end(context); | ||
* function (dc, value) { | ||
* dc.batch.reply(`Recognized value: ${value}`); | ||
* return dc.end(); | ||
* } | ||
@@ -54,3 +39,5 @@ * ]); | ||
*/ | ||
export class ConfirmPrompt implements Dialog { | ||
export class ConfirmPrompt<C extends BotContext> extends Prompt<C, boolean> { | ||
private prompt: prompts.ConfirmPrompt; | ||
/** | ||
@@ -70,7 +57,4 @@ * Allows for the localization of the confirm prompts yes/no choices to other locales besides | ||
*/ | ||
static choices: ConfirmChoices = { '*': ['yes', 'no'] }; | ||
static choices: prompts.ConfirmChoices = { '*': ['yes', 'no'] }; | ||
/** Additional options passed to the `ChoiceStyler` and used to tweak the style of yes/no choices rendered to the user. */ | ||
public readonly stylerOptions: ChoiceStylerOptions; | ||
/** | ||
@@ -82,68 +66,51 @@ * Creates a new instance of the prompt. | ||
* ```JavaScript | ||
* dialogs.add('confirmPrompt', new ConfirmPrompt((context, value) => { | ||
* dialogs.add('confirmPrompt', new ConfirmPrompt((dc, value) => { | ||
* if (value === undefined) { | ||
* context.reply(`Please answer with "yes" or "no".`); | ||
* return Prompts.resolve(); | ||
* dc.batch.reply(`Invalid answer. Answer with "yes" or "no".`); | ||
* return undefined; | ||
* } else { | ||
* return dialogs.end(context, values); | ||
* return value; | ||
* } | ||
* })); | ||
* ``` | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. If the validator replies with a message no additional retry prompt will be sent. | ||
* @param defaultLocale (Optional) locale to use if `dc.context.request.locale` not specified. Defaults to a value of `en-us`. | ||
*/ | ||
constructor(private validator?: PromptValidator<boolean|undefined>) { | ||
this.stylerOptions = { includeNumbers: false }; | ||
constructor(validator?: PromptValidator<C, boolean>, defaultLocale?: string) { | ||
super(validator); | ||
this.prompt = prompts.createConfirmPrompt(undefined, defaultLocale); | ||
this.prompt.choices = ConfirmPrompt.choices; | ||
} | ||
public begin(context: BotContext, dialogs: DialogSet, options: ConfirmPromptOptions): Promise<void> { | ||
// Persist options | ||
const instance = dialogs.getInstance<ConfirmPromptOptions>(context); | ||
instance.state = options || {}; | ||
/** | ||
* Sets additional options passed to the `ChoiceFactory` and used to tweak the style of choices | ||
* rendered to the user. | ||
* @param options Additional options to set. | ||
*/ | ||
public choiceOptions(options: prompts.ChoiceFactoryOptions): this { | ||
Object.assign(this.prompt.choiceOptions, options); | ||
return this; | ||
} | ||
// Send initial prompt | ||
if (instance.state.prompt) { | ||
return this.sendChoicePrompt(context, dialogs, instance.state.prompt, instance.state.speak); | ||
} else { | ||
return Promise.resolve(); | ||
} | ||
/** | ||
* Sets the style of the yes/no choices rendered to the user when prompting. | ||
* @param listStyle Type of list to render to to user. Defaults to `ListStyle.auto`. | ||
*/ | ||
public style(listStyle: prompts.ListStyle): this { | ||
this.prompt.style = listStyle; | ||
return this; | ||
} | ||
public continue(context: BotContext, dialogs: DialogSet): Promise<void> { | ||
// Recognize value | ||
const options = dialogs.getInstance<ConfirmPromptOptions>(context).state; | ||
const utterance = context.request && context.request.text ? context.request.text : ''; | ||
const results = Recognizers.recognizeBoolean(utterance, 'en-us'); | ||
const value = results.length > 0 && results[0].resolution ? results[0].resolution.value : undefined; | ||
if (this.validator) { | ||
// Call validator for further processing | ||
return Promise.resolve(this.validator(context, value, dialogs)); | ||
} else if (typeof value === 'boolean') { | ||
// Return recognized value | ||
return dialogs.end(context, value); | ||
} else if (options.retryPrompt) { | ||
// Send retry prompt to user | ||
return this.sendChoicePrompt(context, dialogs, options.retryPrompt, options.retrySpeak); | ||
protected onPrompt(dc: DialogContext<C>, options: PromptOptions, isRetry: boolean): Promise<void> { | ||
if (isRetry && options.retryPrompt) { | ||
return this.prompt.prompt(dc.context, options.retryPrompt, options.retrySpeak); | ||
} else if (options.prompt) { | ||
// Send original prompt to user | ||
return this.sendChoicePrompt(context, dialogs, options.prompt, options.speak); | ||
} else { | ||
return Promise.resolve(); | ||
return this.prompt.prompt(dc.context, options.prompt, options.speak); | ||
} | ||
return Promise.resolve(); | ||
} | ||
protected sendChoicePrompt(context: BotContext, dialogs: DialogSet, prompt: string|Partial<Activity>, speak?: string): Promise<void> { | ||
if (typeof prompt === 'string') { | ||
// Get locale specific choices | ||
let locale = context.request && context.request.locale ? context.request.locale.toLowerCase() : '*'; | ||
if (!ConfirmPrompt.choices.hasOwnProperty(locale)) { locale = '*' } | ||
const choices = ConfirmPrompt.choices[locale]; | ||
// Reply with formatted prompt | ||
const style = dialogs.getInstance<ConfirmPromptOptions>(context).state.style; | ||
context.reply(formatChoicePrompt(context, choices, prompt, speak, this.stylerOptions, style)) | ||
} else { | ||
context.reply(prompt); | ||
} | ||
return Promise.resolve(); | ||
protected onRecognize(dc: DialogContext<C>, options: PromptOptions): Promise<boolean|undefined> { | ||
return this.prompt.recognize(dc.context); | ||
} | ||
} |
@@ -8,31 +8,8 @@ /** | ||
*/ | ||
import { Dialog } from '../dialog'; | ||
import { DialogSet } from '../dialogSet'; | ||
import { PromptOptions, PromptValidator, formatPrompt } from './prompt'; | ||
import * as Recognizers from '@microsoft/recognizers-text-date-time'; | ||
import { BotContext } from 'botbuilder'; | ||
import { DialogContext } from '../dialogContext'; | ||
import { Prompt, PromptOptions, PromptValidator } from './prompt'; | ||
import * as prompts from 'botbuilder-prompts'; | ||
/** | ||
* Datetime result returned by `DatetimePrompt`. For more details see the LUIS docs for | ||
* [builtin.datetimev2](https://docs.microsoft.com/en-us/azure/cognitive-services/luis/luis-reference-prebuilt-entities#builtindatetimev2). | ||
*/ | ||
export interface FoundDatetime { | ||
/** | ||
* TIMEX expression representing ambiguity of the recognized time. | ||
*/ | ||
timex: string; | ||
/** | ||
* Type of time recognized. Possible values are 'date', 'time', 'datetime', 'daterange', | ||
* 'timerange', 'datetimerange', 'duration', or 'set'. | ||
*/ | ||
type: string; | ||
/** | ||
* Value of the specified [type](#type) that's a reasonable approximation given the ambiguity | ||
* of the [timex](#timex). | ||
*/ | ||
value: string; | ||
} | ||
/** | ||
* Prompts a user to enter a datetime expression. By default the prompt will return to the | ||
@@ -51,8 +28,8 @@ * calling dialog a `FoundDatetime[]` but this can be overridden using a custom `PromptValidator`. | ||
* dialogs.add('datetimeDemo', [ | ||
* function (context) { | ||
* return dialogs.prompt(context, 'datetimePrompt', `datetime: enter a datetime`); | ||
* function (dc) { | ||
* return dc.prompt('datetimePrompt', `datetime: enter a datetime`); | ||
* }, | ||
* function (context, values) { | ||
* context.reply(`Recognized values: ${JSON.stringify(values)}`); | ||
* return dialogs.end(context); | ||
* function (dc, values) { | ||
* dc.batch.reply(`Recognized values: ${JSON.stringify(values)}`); | ||
* return dc.end(); | ||
* } | ||
@@ -62,3 +39,5 @@ * ]); | ||
*/ | ||
export class DatetimePrompt implements Dialog { | ||
export class DatetimePrompt<C extends BotContext> extends Prompt<C, prompts.FoundDatetime[]> { | ||
private prompt: prompts.DatetimePrompt; | ||
/** | ||
@@ -70,52 +49,35 @@ * Creates a new instance of the prompt. | ||
* ```JavaScript | ||
* dialogs.add('timePrompt', new DatetimePrompt((context, values) => { | ||
* dialogs.add('timePrompt', new DatetimePrompt((dc, values) => { | ||
* try { | ||
* if (values.length < 0) { throw new Error('missing time') } | ||
* if (!Array.isArray(values) || values.length < 0) { throw new Error('missing time') } | ||
* if (values[0].type !== 'datetime') { throw new Error('unsupported type') } | ||
* const value = new Date(values[0].value); | ||
* if (value.getTime() < new Date().getTime()) { throw new Error('in the past') } | ||
* return dialogs.end(context, value); | ||
* return value; | ||
* } catch (err) { | ||
* context.reply(`Please enter a valid time in the future like "tomorrow at 9am" or say "cancel".`); | ||
* return Promise.resolve(); | ||
* dc.batch.reply(`Invalid time. Answer with a time in the future like "tomorrow at 9am" or say "cancel".`); | ||
* return undefined; | ||
* } | ||
* })); | ||
* ``` | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. If the validator replies with a message no additional retry prompt will be sent. | ||
* @param defaultLocale (Optional) locale to use if `dc.context.request.locale` not specified. Defaults to a value of `en-us`. | ||
*/ | ||
constructor(private validator?: PromptValidator<FoundDatetime[]>) {} | ||
constructor(validator?: PromptValidator<C, prompts.FoundDatetime[]>, defaultLocale?: string) { | ||
super(validator); | ||
this.prompt = prompts.createDatetimePrompt(undefined, defaultLocale); | ||
} | ||
public begin(context: BotContext, dialogs: DialogSet, options: PromptOptions): Promise<void> { | ||
// Persist options | ||
const instance = dialogs.getInstance<PromptOptions>(context); | ||
instance.state = options || {}; | ||
// Send initial prompt | ||
if (instance.state.prompt) { context.reply(formatPrompt(instance.state.prompt, instance.state.speak)) } | ||
protected onPrompt(dc: DialogContext<C>, options: PromptOptions, isRetry: boolean): Promise<void> { | ||
if (isRetry && options.retryPrompt) { | ||
return this.prompt.prompt(dc.context, options.retryPrompt, options.retrySpeak); | ||
} else if (options.prompt) { | ||
return this.prompt.prompt(dc.context, options.prompt, options.speak); | ||
} | ||
return Promise.resolve(); | ||
} | ||
public continue(context: BotContext, dialogs: DialogSet): Promise<void> { | ||
// Recognize value | ||
const options = dialogs.getInstance<PromptOptions>(context).state; | ||
const utterance = context.request && context.request.text ? context.request.text : ''; | ||
const results = Recognizers.recognizeDateTime(utterance, 'en-us'); | ||
const value: FoundDatetime[] = results.length > 0 && results[0].resolution ? (results[0].resolution.values || []) : []; | ||
if (this.validator) { | ||
// Call validator for further processing | ||
return Promise.resolve(this.validator(context, value, dialogs)); | ||
} else if (value.length > 0) { | ||
// Return recognized value | ||
return dialogs.end(context, value); | ||
} else { | ||
if (options.retryPrompt) { | ||
// Send retry prompt to user | ||
context.reply(formatPrompt(options.retryPrompt, options.retrySpeak)); | ||
} else if (options.prompt) { | ||
// Send original prompt to user | ||
context.reply(formatPrompt(options.prompt, options.speak)); | ||
} | ||
return Promise.resolve(); | ||
} | ||
protected onRecognize(dc: DialogContext<C>, options: PromptOptions): Promise<prompts.FoundDatetime[]|undefined> { | ||
return this.prompt.recognize(dc.context); | ||
} | ||
} |
@@ -8,6 +8,6 @@ /** | ||
*/ | ||
import { Dialog } from '../dialog'; | ||
import { DialogSet } from '../dialogSet'; | ||
import { PromptOptions, PromptValidator, formatPrompt } from './prompt'; | ||
import * as Recognizers from '@microsoft/recognizers-text-number'; | ||
import { BotContext } from 'botbuilder'; | ||
import { DialogContext } from '../dialogContext'; | ||
import { Prompt, PromptOptions, PromptValidator } from './prompt'; | ||
import * as prompts from 'botbuilder-prompts'; | ||
@@ -28,8 +28,8 @@ /** | ||
* dialogs.add('numberDemo', [ | ||
* function (context) { | ||
* return dialogs.prompt(context, 'numberPrompt', `number: enter a number`); | ||
* function (dc) { | ||
* return dc.prompt('numberPrompt', `number: enter a number`); | ||
* }, | ||
* function (context, value) { | ||
* context.reply(`Recognized value: ${value}`); | ||
* return dialogs.end(context); | ||
* function (dc, value) { | ||
* dc.batch.reply(`Recognized value: ${value}`); | ||
* return dc.end(); | ||
* } | ||
@@ -39,3 +39,5 @@ * ]); | ||
*/ | ||
export class NumberPrompt implements Dialog { | ||
export class NumberPrompt<C extends BotContext> extends Prompt<C, number> { | ||
private prompt: prompts.NumberPrompt; | ||
/** | ||
@@ -47,48 +49,33 @@ * Creates a new instance of the prompt. | ||
* ```JavaScript | ||
* dialogs.add('agePrompt', new NumberPrompt((context, value) => { | ||
* dialogs.add('agePrompt', new NumberPrompt((dc, value) => { | ||
* if (value === undefined || value < 1 || value > 110) { | ||
* context.reply(`Please enter a valid age between 1 and 110.`); | ||
* return Promise.resolve(); | ||
* dc.batch.reply(`Invalid age. Only ages between 1 and 110 are allowed.`); | ||
* return undefined; | ||
* } else { | ||
* return dialogs.end(context, value); | ||
* return value; | ||
* } | ||
* })); | ||
* ``` | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. If the validator replies with a message no additional retry prompt will be sent. | ||
* @param defaultLocale (Optional) locale to use if `dc.context.request.locale` not specified. Defaults to a value of `en-us`. | ||
*/ | ||
constructor(private validator?: PromptValidator<number|undefined>) {} | ||
constructor(validator?: PromptValidator<C, number>, defaultLocale?: string) { | ||
super(validator); | ||
this.prompt = prompts.createNumberPrompt(undefined, defaultLocale); | ||
} | ||
public begin(context: BotContext, dialogs: DialogSet, options: PromptOptions): Promise<void> { | ||
// Persist options | ||
const instance = dialogs.getInstance<PromptOptions>(context); | ||
instance.state = options || {}; | ||
// Send initial prompt | ||
if (instance.state.prompt) { context.reply(formatPrompt(instance.state.prompt, instance.state.speak)) } | ||
protected onPrompt(dc: DialogContext<C>, options: PromptOptions, isRetry: boolean): Promise<void> { | ||
if (isRetry && options.retryPrompt) { | ||
return this.prompt.prompt(dc.context, options.retryPrompt, options.retrySpeak); | ||
} else if (options.prompt) { | ||
return this.prompt.prompt(dc.context, options.prompt, options.speak); | ||
} | ||
return Promise.resolve(); | ||
} | ||
public continue(context: BotContext, dialogs: DialogSet): Promise<void> { | ||
// Recognize value | ||
const options = dialogs.getInstance<PromptOptions>(context).state; | ||
const utterance = context.request && context.request.text ? context.request.text : ''; | ||
const results = Recognizers.recognizeNumber(utterance, 'en-us'); | ||
const value = results.length > 0 && results[0].resolution ? parseFloat(results[0].resolution.value) : undefined; | ||
if (this.validator) { | ||
// Call validator for further processing | ||
return Promise.resolve(this.validator(context, value, dialogs)); | ||
} else if (typeof value === 'number') { | ||
// Return recognized value | ||
return dialogs.end(context, value); | ||
} else { | ||
if (options.retryPrompt) { | ||
// Send retry prompt to user | ||
context.reply(formatPrompt(options.retryPrompt, options.retrySpeak)); | ||
} else if (options.prompt) { | ||
// Send original prompt to user | ||
context.reply(formatPrompt(options.prompt, options.speak)); | ||
} | ||
return Promise.resolve(); | ||
} | ||
protected onRecognize(dc: DialogContext<C>, options: PromptOptions): Promise<number|undefined> { | ||
return this.prompt.recognize(dc.context); | ||
} | ||
} | ||
@@ -8,4 +8,5 @@ /** | ||
*/ | ||
import { Activity, Promiseable, InputHints } from 'botbuilder'; | ||
import { DialogSet } from '../dialogSet'; | ||
import { BotContext, Activity, Promiseable } from 'botbuilder'; | ||
import { DialogContext } from '../dialogContext'; | ||
import { Control } from '../control'; | ||
@@ -32,27 +33,50 @@ /** Basic configuration options supported by all prompts. */ | ||
* recognized. | ||
* @param T Possible types for `value` arg. | ||
* @param PromptValidator.context Context object for the current turn of conversation with the user. | ||
* @param C Type of dialog context object passed to validator. | ||
* @param R Type of value that will recognized and passed to the validator as input. | ||
* @param O Type of output that will be returned by the validator. This can be changed from the input type by the validator. | ||
* @param PromptValidator.context Dialog context for the current turn of conversation with the user. | ||
* @param PromptValidator.value The value that was recognized or wasn't recognized. Depending on the prompt this can be either undefined or an empty array to indicate an unrecognized value. | ||
* @param PromptValidator.dialogs The parent dialog set. | ||
*/ | ||
export type PromptValidator<T> = (context: BotContext, value: T, dialogs: DialogSet) => Promiseable<void>; | ||
export type PromptValidator<C extends BotContext, R> = (dc: DialogContext<C>, value: R|undefined) => Promiseable<any>; | ||
/** | ||
* Helper function to properly format a prompt sent to a user. | ||
* | ||
* **Example usage:** | ||
* | ||
* ```JavaScript | ||
* const { formatPrompt } = require('botbuilder-dialogs'); | ||
* | ||
* context.reply(formatPrompt(`Hi... What's your name?`, `What is your name?`)); | ||
* ``` | ||
* @param prompt Activity or text to prompt the user with. If prompt is a `string` then an activity of type `message` will be created. | ||
* @param speak (Optional) SSML to speak to the user on channels like Cortana. The messages `inputHint` will be automatically set to `InputHints.expectingInput`. | ||
*/ | ||
export function formatPrompt(prompt: string|Partial<Activity>, speak?: string): Partial<Activity> { | ||
const p = typeof prompt === 'string' ? { type: 'message', text: prompt } as Partial<Activity> : prompt; | ||
if (speak) { p.speak = speak } | ||
if (!p.inputHint) { p.inputHint = InputHints.ExpectingInput } | ||
return p; | ||
export abstract class Prompt<C extends BotContext, T> extends Control<C> { | ||
constructor(private validator?: PromptValidator<C, T>) { | ||
super(); | ||
} | ||
protected abstract onPrompt(dc: DialogContext<C>, options: PromptOptions, isRetry: boolean): Promise<any>; | ||
protected abstract onRecognize(dc: DialogContext<C>, options: PromptOptions): Promise<T|undefined>; | ||
public dialogBegin(dc: DialogContext<C>, options: PromptOptions): Promise<any> { | ||
// Persist options | ||
const instance = dc.instance; | ||
instance.state = options || {}; | ||
// Send initial prompt | ||
return this.onPrompt(dc, instance.state, false); | ||
} | ||
public dialogContinue(dc: DialogContext<C>): Promise<any> { | ||
// Recognize value | ||
const instance = dc.instance; | ||
return this.onRecognize(dc, instance.state) | ||
.then((recognized) => { | ||
if (this.validator) { | ||
// Call validator | ||
return Promise.resolve(this.validator(dc, recognized)); | ||
} else { | ||
// Pass through recognized value | ||
return recognized; | ||
} | ||
}).then((output) => { | ||
if (output !== undefined) { | ||
// Return recognized value | ||
return dc.end(output); | ||
} else if (!dc.context.responded) { | ||
// Send retry prompt | ||
return this.onPrompt(dc, instance.state, true); | ||
} | ||
}); | ||
} | ||
} |
@@ -8,5 +8,6 @@ /** | ||
*/ | ||
import { Dialog } from '../dialog'; | ||
import { DialogSet } from '../dialogSet'; | ||
import { PromptOptions, PromptValidator, formatPrompt } from './prompt'; | ||
import { BotContext } from 'botbuilder'; | ||
import { DialogContext } from '../dialogContext'; | ||
import { Prompt, PromptOptions, PromptValidator } from './prompt'; | ||
import * as prompts from 'botbuilder-prompts'; | ||
@@ -27,8 +28,8 @@ /** | ||
* dialogs.add('textDemo', [ | ||
* function (context) { | ||
* return dialogs.prompt(context, 'textPrompt', `text: enter some text`); | ||
* function (dc) { | ||
* return dc.prompt('textPrompt', `text: enter some text`); | ||
* }, | ||
* function (context, value) { | ||
* context.reply(`Recognized value: ${value}`); | ||
* return dialogs.end(context); | ||
* function (dc, value) { | ||
* dc.batch.reply(`Recognized value: ${value}`); | ||
* return dc.end(); | ||
* } | ||
@@ -38,3 +39,5 @@ * ]); | ||
*/ | ||
export class TextPrompt implements Dialog { | ||
export class TextPrompt<C extends BotContext> extends Prompt<C, string> { | ||
private prompt: prompts.TextPrompt; | ||
/** | ||
@@ -46,35 +49,30 @@ * Creates a new instance of the prompt. | ||
* ```JavaScript | ||
* dialogs.add('titlePrompt', new TextPrompt((context, value) => { | ||
* if (value.length < 3) { | ||
* context.reply(`Title should be at least 3 characters long.`); | ||
* return Promise.resolve(); | ||
* dialogs.add('titlePrompt', new TextPrompt((dc, value) => { | ||
* if (!value || value.length < 3) { | ||
* dc.batch.reply(`Title should be at least 3 characters long.`); | ||
* return undefined; | ||
* } else { | ||
* return dialogs.end(context, value.trim()); | ||
* return value.trim(); | ||
* } | ||
* })); | ||
* ``` | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. | ||
* @param validator (Optional) validator that will be called each time the user responds to the prompt. If the validator replies with a message no additional retry prompt will be sent. | ||
*/ | ||
constructor(private validator?: PromptValidator<string>) {} | ||
constructor(validator?: PromptValidator<C, string>) { | ||
super(validator); | ||
this.prompt = prompts.createTextPrompt(); | ||
} | ||
public begin(context: BotContext, dialogs: DialogSet, options: PromptOptions): Promise<void> { | ||
// Persist options | ||
const instance = dialogs.getInstance<PromptOptions>(context); | ||
instance.state = options || {}; | ||
// Send initial prompt | ||
if (instance.state.prompt) { context.reply(formatPrompt(instance.state.prompt, instance.state.speak)) } | ||
protected onPrompt(dc: DialogContext<C>, options: PromptOptions, isRetry: boolean): Promise<void> { | ||
if (isRetry && options.retryPrompt) { | ||
return this.prompt.prompt(dc.context, options.retryPrompt, options.retrySpeak); | ||
} else if (options.prompt) { | ||
return this.prompt.prompt(dc.context, options.prompt, options.speak); | ||
} | ||
return Promise.resolve(); | ||
} | ||
public continue(context: BotContext, dialogs: DialogSet): Promise<void> { | ||
// Recognize value and call validator | ||
const utterance = context.request && context.request.text ? context.request.text : ''; | ||
if (this.validator) { | ||
return Promise.resolve(this.validator(context, utterance, dialogs)); | ||
} else { | ||
// Default behavior is to just return recognized value | ||
return dialogs.end(context, utterance); | ||
} | ||
protected onRecognize(dc: DialogContext<C>, options: PromptOptions): Promise<string|undefined> { | ||
return this.prompt.recognize(dc.context); | ||
} | ||
} |
@@ -8,6 +8,5 @@ /** | ||
*/ | ||
import { Promiseable, BotService } from 'botbuilder'; | ||
import { Promiseable, BotContext } from 'botbuilder'; | ||
import { Dialog, DialogInstance } from './dialog'; | ||
import { DialogSet } from './dialogSet'; | ||
import { Context } from 'vm'; | ||
import { DialogContext } from './dialogContext'; | ||
@@ -53,3 +52,3 @@ /** | ||
*/ | ||
export type WaterfallStep = (context: BotContext, args?: any, next?: SkipStepFunction) => Promiseable<void>; | ||
export type WaterfallStep<C extends BotContext> = (dc: DialogContext<C>, args?: any, next?: SkipStepFunction) => Promiseable<any>; | ||
@@ -60,3 +59,3 @@ /** | ||
*/ | ||
export type SkipStepFunction = (args?: any) => Promise<void>; | ||
export type SkipStepFunction = (args?: any) => Promise<any>; | ||
@@ -66,17 +65,69 @@ /** | ||
* functions which will be executed in sequence. Each waterfall step can ask a question of the user | ||
* by calling either a prompt or another dialog. When the called dialog completes control will be | ||
* returned to the next step of the waterfall and any input collected by the prompt or other dialog | ||
* will be passed to the step as an argument. | ||
* and the users response will be passed as an argument to the next waterfall step. | ||
* | ||
* When a step is executed it should call either `context.begin()`, `context.end()`, | ||
* `context.replace()`, `context.cancelDialog()`, or a prompt. Failing to do so will result | ||
* in the dialog automatically ending the next time the user replies. | ||
* For simple text questions you can send the user a message and then process their answer in the | ||
* next step: | ||
* | ||
* Similarly, calling a dialog/prompt from within the last step of the waterfall will result in | ||
* the waterfall automatically ending once the dialog/prompt completes. This is often desired | ||
* though as the result from tha called dialog/prompt will be passed to the waterfalls parent | ||
* dialog. | ||
* ```JS | ||
* dialogs.add('namePrompt', [ | ||
* async function (dc) { | ||
* dc.instance.state = { first: '', last: '', full: '' }; | ||
* await dc.context.sendActivity(`What's your first name?`); | ||
* }, | ||
* async function (dc, firstName) { | ||
* dc.instance.state.first = firstName; | ||
* await dc.context.sendActivity(`Great ${firstName}! What's your last name?`); | ||
* }, | ||
* async function (dc, lastName) { | ||
* const name = dc.instance.state; | ||
* name.last = lastName; | ||
* name.full = name.first + ' ' + name.last; | ||
* await dc.end(name); | ||
* } | ||
* ]); | ||
* ``` | ||
* | ||
* For more complex sequences you can call other dialogs from within a step and the result returned | ||
* by the dialog will be passed to the next step: | ||
* | ||
* ```JS | ||
* dialogs.add('survey', [ | ||
* async function (dc) { | ||
* dc.instance.state = { name: undefined, languages: '', years: 0 }; | ||
* await dc.begin('namePrompt'); | ||
* }, | ||
* async function (dc, name) { | ||
* dc.instance.state.name = name; | ||
* await dc.context.sendActivity(`Ok ${name.full}... What programming languages do you know?`); | ||
* }, | ||
* async function (dc, languages) { | ||
* dc.instance.state.languages = languages; | ||
* await dc.prompt('yearsPrompt', `Great. So how many years have you been programming?`); | ||
* }, | ||
* async function (dc, years) { | ||
* dc.instance.state.years = years; | ||
* await dc.context.sendActivity(`Thank you for taking our survey.`); | ||
* await dc.end(dc.instance.state); | ||
* } | ||
* ]); | ||
* | ||
* dialogs.add('yearsPrompt', new NumberPrompt(async (dc, value) => { | ||
* if (value === undefined || value < 0 || value > 110) { | ||
* await dc.context.sendActivity(`Enter a number from 0 to 110.`); | ||
* } else { | ||
* return value; | ||
* } | ||
* })); | ||
* ``` | ||
* | ||
* The example builds on the previous `namePrompt` sample and shows how you can call another dialog | ||
* which will ask its own sequence of questions. The dialogs library provides a built-in set of | ||
* prompt classes which can be used to recognize things like dates and numbers in the users response. | ||
* | ||
* You should generally call `dc.end()` or `dc.replace()` from your last waterfall step but if you fail | ||
* to do that the dialog will be automatically ended for you on the users next reply. The users | ||
* response will be passed to the calling dialogs next waterfall step if there is one. | ||
*/ | ||
export class Waterfall implements Dialog { | ||
private readonly steps: WaterfallStep[]; | ||
export class Waterfall<C extends BotContext> implements Dialog<C> { | ||
private readonly steps: WaterfallStep<C>[]; | ||
@@ -87,32 +138,38 @@ /** | ||
*/ | ||
constructor(steps: WaterfallStep[]) { | ||
constructor(steps: WaterfallStep<C>[]) { | ||
this.steps = (steps || []).slice(0); | ||
} | ||
public begin(context: BotContext, dialogs: DialogSet, args?: any): Promiseable<void> { | ||
const instance = dialogs.getInstance(context) as WaterfallInstance<any>; | ||
public dialogBegin(dc: DialogContext<C>, args?: any): Promiseable<any> { | ||
const instance = dc.instance as WaterfallInstance<any>; | ||
instance.step = 0; | ||
return this.runStep(context, dialogs, args); | ||
return this.runStep(dc, args); | ||
} | ||
public resume(context: BotContext, dialogs: DialogSet, result?: any): Promiseable<void> { | ||
const instance = dialogs.getInstance(context) as WaterfallInstance<any>; | ||
public dialogContinue(dc: DialogContext<C>): Promise<any> { | ||
const instance = dc.instance as WaterfallInstance<any>; | ||
instance.step += 1 | ||
return this.runStep(context, dialogs, result); | ||
return this.runStep(dc, dc.context.request.text || ''); | ||
} | ||
private runStep(context: BotContext, dialogs: DialogSet, result?: any): Promise<void> { | ||
public dialogResume(dc: DialogContext<C>, result?: any): Promiseable<any> { | ||
const instance = dc.instance as WaterfallInstance<any>; | ||
instance.step += 1 | ||
return this.runStep(dc, result); | ||
} | ||
private runStep(dc: DialogContext<C>, result?: any): Promise<any> { | ||
try { | ||
const instance = dialogs.getInstance(context) as WaterfallInstance<any>; | ||
const instance = dc.instance as WaterfallInstance<any>; | ||
const step = instance.step; | ||
if (step >= 0 && step < this.steps.length) { | ||
// Execute step | ||
return Promise.resolve(this.steps[step](context, result, (r?: any) => { | ||
return Promise.resolve(this.steps[step](dc, result, (r?: any) => { | ||
// Skip to next step | ||
instance.step += 1; | ||
return this.runStep(context, r); | ||
return this.runStep(dc, r); | ||
})); | ||
} else { | ||
// End of waterfall so just return to parent | ||
return dialogs.end(context); | ||
return dc.end(result); | ||
} | ||
@@ -119,0 +176,0 @@ } catch (err) { |
{ | ||
"compilerOptions": { | ||
/* Basic Options */ | ||
"target": "ES2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */ | ||
"module": "commonjs", /* Specify module code generation: 'commonjs', 'amd', 'system', 'umd' or 'es2015'. */ | ||
// "lib": [], /* Specify library files to be included in the compilation: */ | ||
// "allowJs": true, /* Allow javascript files to be compiled. */ | ||
// "checkJs": true, /* Report errors in .js files. */ | ||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ | ||
"declaration": true, /* Generates corresponding '.d.ts' file. */ | ||
"sourceMap": true, /* Generates corresponding '.map' file. */ | ||
// "outFile": "./", /* Concatenate and emit output to single file. */ | ||
"outDir": "./lib", /* Redirect output structure to the directory. */ | ||
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ | ||
// "removeComments": true, /* Do not emit comments to output. */ | ||
// "noEmit": true, /* Do not emit outputs. */ | ||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */ | ||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ | ||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ | ||
/* Strict Type-Checking Options */ | ||
"strict": true, /* Enable all strict type-checking options. */ | ||
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ | ||
// "strictNullChecks": true, /* Enable strict null checks. */ | ||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ | ||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ | ||
/* Additional Checks */ | ||
// "noUnusedLocals": true, /* Report errors on unused locals. */ | ||
// "noUnusedParameters": true, /* Report errors on unused parameters. */ | ||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ | ||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ | ||
/* Module Resolution Options */ | ||
"moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ | ||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ | ||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ | ||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ | ||
// "typeRoots": [], /* List of folders to include type definitions from. */ | ||
// "types": [], /* Type declaration files to be included in compilation. */ | ||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ | ||
/* Source Map Options */ | ||
// "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ | ||
// "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ | ||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ | ||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ | ||
/* Experimental Options */ | ||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ | ||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ | ||
} | ||
"target": "ES2015", | ||
"module": "commonjs", | ||
"declaration": true, | ||
"sourceMap": true, | ||
"outDir": "./lib", | ||
"rootDir": "./src", | ||
"types" : ["node"] | ||
}, | ||
"include": [ | ||
"src/**/*" | ||
] | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
65
148476
3226
1
+ Added@types/caseless@0.12.5(transitive)
+ Added@types/form-data@2.5.2(transitive)
+ Added@types/is-stream@1.1.0(transitive)
+ Added@types/node@22.10.29.6.61(transitive)
+ Added@types/node-fetch@1.6.9(transitive)
+ Added@types/request@2.48.12(transitive)
+ Added@types/tough-cookie@4.0.5(transitive)
+ Added@types/uuid@3.4.13(transitive)
+ Addedajv@5.5.2(transitive)
+ Addedasn1@0.2.6(transitive)
+ Addedassert-plus@1.0.0(transitive)
+ Addedasync-file@2.0.2(transitive)
+ Addedasynckit@0.4.0(transitive)
+ Addedaws-sign2@0.7.0(transitive)
+ Addedaws4@1.13.2(transitive)
+ Addedbalanced-match@1.0.2(transitive)
+ Addedbase64url@2.0.0(transitive)
+ Addedbcrypt-pbkdf@1.0.2(transitive)
+ Addedboom@4.3.15.3.3(transitive)
+ Addedbotbuilder@4.0.0-m2.1(transitive)
+ Addedbotbuilder-choices@4.0.0-m2.1(transitive)
+ Addedbotbuilder-core@4.0.0-m2.1(transitive)
+ Addedbotbuilder-core-extensions@4.0.0-m2.1(transitive)
+ Addedbotbuilder-prompts@4.0.0-m2.1(transitive)
+ Addedbotframework-connector@4.0.0-m2.1(transitive)
+ Addedbotframework-schema@4.0.0-m2.1(transitive)
+ Addedbrace-expansion@1.1.11(transitive)
+ Addedbuffer-equal-constant-time@1.0.1(transitive)
+ Addedcaseless@0.12.0(transitive)
+ Addedco@4.6.0(transitive)
+ Addedcombined-stream@1.0.8(transitive)
+ Addedconcat-map@0.0.1(transitive)
+ Addedcore-util-is@1.0.2(transitive)
+ Addedcryptiles@3.2.1(transitive)
+ Addeddashdash@1.14.1(transitive)
+ Addeddelayed-stream@1.0.0(transitive)
+ Addedecc-jsbn@0.1.2(transitive)
+ Addedecdsa-sig-formatter@1.0.11(transitive)
+ Addedes6-denodeify@0.1.5(transitive)
+ Addedescape-string-regexp@1.0.5(transitive)
+ Addedextend@3.0.2(transitive)
+ Addedextsprintf@1.3.0(transitive)
+ Addedfast-deep-equal@1.1.0(transitive)
+ Addedfast-json-stable-stringify@2.1.0(transitive)
+ Addedfetch-cookie@0.7.3(transitive)
+ Addedfilename-reserved-regex@2.0.0(transitive)
+ Addedfilenamify@2.1.0(transitive)
+ Addedforever-agent@0.6.1(transitive)
+ Addedform-data@2.3.32.5.2(transitive)
+ Addedfs.realpath@1.0.0(transitive)
+ Addedgetpass@0.1.7(transitive)
+ Addedglob@7.2.3(transitive)
+ Addedhar-schema@2.0.0(transitive)
+ Addedhar-validator@5.0.3(transitive)
+ Addedhawk@6.0.2(transitive)
+ Addedhoek@4.3.1(transitive)
+ Addedhttp-signature@1.2.0(transitive)
+ Addedinflight@1.0.6(transitive)
+ Addedinherits@2.0.4(transitive)
+ Addedis-buffer@2.0.5(transitive)
+ Addedis-stream@1.1.0(transitive)
+ Addedis-typedarray@1.0.0(transitive)
+ Addedisstream@0.1.2(transitive)
+ Addedjsbn@0.1.1(transitive)
+ Addedjson-schema@0.4.0(transitive)
+ Addedjson-schema-traverse@0.3.1(transitive)
+ Addedjson-stringify-safe@5.0.1(transitive)
+ Addedjsonwebtoken@8.0.1(transitive)
+ Addedjsprim@1.4.2(transitive)
+ Addedjwa@1.4.1(transitive)
+ Addedjws@3.2.2(transitive)
+ Addedlodash.includes@4.3.0(transitive)
+ Addedlodash.isboolean@3.0.3(transitive)
+ Addedlodash.isinteger@4.0.4(transitive)
+ Addedlodash.isnumber@3.0.3(transitive)
+ Addedlodash.isplainobject@4.0.6(transitive)
+ Addedlodash.isstring@4.0.1(transitive)
+ Addedlodash.once@4.1.1(transitive)
+ Addedmime-db@1.52.0(transitive)
+ Addedmime-types@2.1.35(transitive)
+ Addedminimatch@3.1.2(transitive)
+ Addedmoment@2.30.1(transitive)
+ Addedms@2.1.3(transitive)
+ Addedms-rest-js@0.2.8(transitive)
+ Addedoauth-sign@0.8.2(transitive)
+ Addedonce@1.4.0(transitive)
+ Addedpath-is-absolute@1.0.1(transitive)
+ Addedperformance-now@2.1.0(transitive)
+ Addedpsl@1.15.0(transitive)
+ Addedpunycode@1.4.12.3.1(transitive)
+ Addedqs@6.5.3(transitive)
+ Addedquerystringify@2.2.0(transitive)
+ Addedreadline@1.3.0(transitive)
+ Addedrequest@2.83.0(transitive)
+ Addedrequires-port@1.0.0(transitive)
+ Addedrimraf@2.7.1(transitive)
+ Addedrsa-pem-from-mod-exp@0.8.6(transitive)
+ Addedsafe-buffer@5.2.1(transitive)
+ Addedsafer-buffer@2.1.2(transitive)
+ Addedsntp@2.1.0(transitive)
+ Addedsshpk@1.18.0(transitive)
+ Addedstringstream@0.0.6(transitive)
+ Addedstrip-outer@1.0.1(transitive)
+ Addedtough-cookie@2.3.42.5.0(transitive)
+ Addedtrim-repeated@1.0.0(transitive)
+ Addedtunnel-agent@0.6.0(transitive)
+ Addedtweetnacl@0.14.5(transitive)
+ Addedundici-types@6.20.0(transitive)
+ Addedurl-parse@1.5.10(transitive)
+ Addeduuid@3.4.0(transitive)
+ Addedverror@1.10.0(transitive)
+ Addedwrappy@1.0.2(transitive)
+ Addedxtend@4.0.2(transitive)
- Removedbotbuilder-choices@4.0.0-m1.10
- Removedbotbuilder@4.0.0-m1.10(transitive)
- Removedbotbuilder-choices@4.0.0-m1.10(transitive)
- Removedbotframework-schema@4.0.0-m1.10(transitive)
Updatedbotbuilder@4.0.0-m2.1