botbuilder-dialogs-adaptive
Version:
Rule system for the Microsoft BotBuilder dialog system.
142 lines (127 loc) • 5.49 kB
text/typescript
/**
* @module botbuilder-dialogs-adaptive
*/
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { ArrayProperty, StringProperty } from '../properties';
import { SendActivity, SendActivityConfiguration } from '../actions/sendActivity';
import {
ArrayExpression,
ArrayExpressionConverter,
StringExpression,
StringExpressionConverter,
} from 'adaptive-expressions';
import {
Converter,
ConverterFactory,
DialogContext,
DialogEvent,
DialogPath,
DialogTurnResult,
DialogTurnStatus,
TurnPath,
} from 'botbuilder-dialogs';
import { StringUtils } from 'botbuilder';
import { ActivityTemplate } from '..';
export interface AskConfiguration extends SendActivityConfiguration {
expectedProperties?: ArrayProperty<string>;
defaultOperation?: StringProperty;
}
/**
* Ask for an open-ended response.
* This sends an activity and then terminates the turn with `DialogTurnStatus.completeAndWait`.
* The next activity from the user will then be handled by the parent adaptive dialog.
* It also builds in a model of the properties that are expected in response through `DialogPath.expectedProperties`.
* `DialogPath.retries` is updated as the same question is asked multiple times.
*/
export class Ask extends SendActivity implements AskConfiguration {
static $kind = 'Microsoft.Ask';
/**
*Initializes a new instance of the [Ask](xref:botbuilder-dialogs-adaptive.Ask) class.
*
* @param text Optional, text value.
* @param expectedProperties Optional, [ArrayExpression](xref:adaptive-expressions.ArrayExpression) of expected properties.
*/
constructor(text?: string, expectedProperties?: ArrayExpression<string>) {
super(text);
this.expectedProperties = expectedProperties;
}
/**
* Gets or sets properties expected to be filled by response.
*/
expectedProperties: ArrayExpression<string>;
/**
* Gets or sets the default operation that will be used when no operation is recognized.
*/
defaultOperation: StringExpression;
/**
* Called when the [Dialog](xref:botbuilder-dialogs.Dialog) is started and pushed onto the dialog stack.
*
* @param property The key of the conditional selector configuration.
* @returns The converter for the selector configuration.
*/
getConverter(property: keyof AskConfiguration): Converter | ConverterFactory {
switch (property) {
case 'expectedProperties':
return new ArrayExpressionConverter<string>();
case 'defaultOperation':
return new StringExpressionConverter();
default:
return super.getConverter(property);
}
}
/**
* Called when the dialog is started and pushed onto the dialog stack.
*
* @summary
* If the task is successful, the result indicates whether the dialog is still
* active after the turn has been processed by the dialog.
*
* You can use the [options](#options) parameter to include the QnA Maker context data,
* which represents context from the previous query. To do so, the value should include a
* `context` property of type [QnAResponseContext](#QnAResponseContext).
*
* @param {DialogContext} dc The [DialogContext](xref:botbuilder-dialogs.DialogContext) for the current turn of conversation.
* @param {object} options (Optional) Initial information to pass to the dialog.
* @returns {Promise<DialogTurnResult>} A promise resolving to the turn result
*/
async beginDialog(dc: DialogContext, options?: object): Promise<DialogTurnResult> {
// get number of retries from memory
let retries: number = dc.state.getValue<number>(DialogPath.retries, 0);
const expected: string[] = this.expectedProperties ? this.expectedProperties.getValue(dc.state) : undefined;
const trigger: DialogEvent = dc.state.getValue<DialogEvent>(TurnPath.dialogEvent);
const lastExpectedProperties: string[] = dc.state.getValue<string[]>(DialogPath.expectedProperties);
const lastTrigger: DialogEvent = dc.state.getValue<DialogEvent>(DialogPath.lastTriggerEvent);
if (
expected &&
lastExpectedProperties &&
lastTrigger &&
!expected.some(
(prop: string): boolean =>
!lastExpectedProperties.some((lastProp: string): boolean => lastProp === prop),
) &&
!lastExpectedProperties.some(
(lastProp: string): boolean => !expected.some((prop: string): boolean => prop === lastProp),
) &&
lastTrigger.name === trigger.name
) {
retries++;
} else {
retries = 0;
}
dc.state.setValue(DialogPath.retries, retries);
dc.state.setValue(DialogPath.lastTriggerEvent, trigger);
dc.state.setValue(DialogPath.expectedProperties, expected);
const result = await super.beginDialog(dc, options);
result.status = DialogTurnStatus.completeAndWait;
return result;
}
protected onComputeId(): string {
if (this.activity instanceof ActivityTemplate) {
return `Ask[${StringUtils.ellipsis(this.activity.template.trim(), 30)}]`;
}
return `Ask[${StringUtils.ellipsis(this.activity && this.activity.toString().trim(), 30)}]`;
}
}