我需要为聊天机器人实现上下文帮助。我的策略是使用活动提示作为带有帮助文本行的表的索引。我正在努力在瀑布对话框中的stepContext.replaceDialog()之后找到活动提示。
我将以Compex Dialog sample为例。
在下面的reviewSelectionDialog中,有一个名为CHOICE_PROMPT的提示。这是我想添加上下文帮助的提示。如果用户输入帮助,则应显示有关该提示的帮助文本。
在同一对话框中是一个循环步骤。根据用户的决定,使用replaceDialog()方法重复(循环)对话框。
ReviewSelectionDialog通过CancelAndHelpDialog进行了扩展。结果,我能够检查并处理诸如“帮助”之类的任何用户中断。
在CancelAndHelpDialog中,当用户输入帮助时,我需要活动提示,以便能够显示相关帮助。 (在此示例中为CHOICE_PROMPT)。
在ReviewSelectionDialog的第一遍中,发送“帮助”后,我可以通过innerDc.activeDialog.id在CancelAndHelpDialog中获得活动提示。但是,在loopStep中的stepContext.replaceDialog()并在CHOICE_PROMPT中再次发送“帮助”之后,innerDc.activeDialog.id显示为REVIEW_SELECTION_DIALOG。 在replace_dialog()之后我在哪里找到活动的提示?
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
const { ChoicePrompt, WaterfallDialog } = require('botbuilder-dialogs');
const REVIEW_SELECTION_DIALOG = 'REVIEW_SELECTION_DIALOG';
const { CancelAndHelpDialog } = require('./cancelAndHelpDialog');
const CHOICE_PROMPT = 'CHOICE_PROMPT';
const WATERFALL_DIALOG = 'WATERFALL_DIALOG';
class ReviewSelectionDialog extends CancelAndHelpDialog {
constructor() {
super(REVIEW_SELECTION_DIALOG);
// Define a "done" response for the company selection prompt.
this.doneOption = 'done';
// Define value names for values tracked inside the dialogs.
this.companiesSelected = 'value-companiesSelected';
// Define the company choices for the company selection prompt.
this.companyOptions = ['Adatum Corporation', 'Contoso Suites', 'Graphic Design Institute', 'Wide World Importers'];
this.addDialog(new ChoicePrompt(CHOICE_PROMPT));
this.addDialog(new WaterfallDialog(WATERFALL_DIALOG, [
this.selectionStep.bind(this),
this.loopStep.bind(this)
]));
this.initialDialogId = WATERFALL_DIALOG;
}
async selectionStep(stepContext) {
// Continue using the same selection list, if any, from the previous iteration of this dialog.
const list = Array.isArray(stepContext.options) ? stepContext.options : [];
stepContext.values[this.companiesSelected] = list;
// Create a prompt message.
let message = '';
if (list.length === 0) {
message = `Please choose a company to review, or \`${ this.doneOption }\` to finish.`;
} else {
message = `You have selected **${ list[0] }**. You can review an additional company, or choose \`${ this.doneOption }\` to finish.`;
}
// Create the list of options to choose from.
const options = list.length > 0
? this.companyOptions.filter(function(item) { return item !== list[0]; })
: this.companyOptions.slice();
options.push(this.doneOption);
// Prompt the user for a choice.
return await stepContext.prompt(CHOICE_PROMPT, {
prompt: message,
retryPrompt: 'Please choose an option from the list.',
choices: options
});
}
async loopStep(stepContext) {
// Retrieve their selection list, the choice they made, and whether they chose to finish.
const list = stepContext.values[this.companiesSelected];
const choice = stepContext.result;
const done = choice.value === this.doneOption;
if (!done) {
// If they chose a company, add it to the list.
list.push(choice.value);
}
if (done || list.length > 1) {
// If they're done, exit and return their list.
return await stepContext.endDialog(list);
} else {
// Otherwise, repeat this dialog, passing in the list from this iteration.
return await stepContext.replaceDialog(REVIEW_SELECTION_DIALOG, list);
}
}
}
module.exports.ReviewSelectionDialog = ReviewSelectionDialog;
module.exports.REVIEW_SELECTION_DIALOG = REVIEW_SELECTION_DIALOG;
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
const { InputHints } = require('botbuilder');
const { ComponentDialog, DialogTurnStatus } = require('botbuilder-dialogs');
/**
* This base class watches for common phrases like "help" and "cancel" and takes action on them
* BEFORE they reach the normal bot logic.
*/
class CancelAndHelpDialog extends ComponentDialog {
async onContinueDialog(innerDc) {
const result = await this.interrupt(innerDc);
if (result) {
return result;
}
return await super.onContinueDialog(innerDc);
}
async interrupt(innerDc) {
if (innerDc.context.activity.text) {
const text = innerDc.context.activity.text.toLowerCase();
switch (text) {
case 'help':
case '?': {
const helpMessageText = 'Show help about prompt: ' + innerDc.activeDialog.id;
await innerDc.context.sendActivity(helpMessageText, helpMessageText, InputHints.ExpectingInput);
return { status: DialogTurnStatus.waiting };
}
case 'cancel':
case 'quit': {
const cancelMessageText = 'Cancelling...';
await innerDc.context.sendActivity(cancelMessageText, cancelMessageText, InputHints.IgnoringInput);
return await innerDc.cancelAllDialogs();
}
}
}
}
}
module.exports.CancelAndHelpDialog = CancelAndHelpDialog;
答案 0 :(得分:1)
感谢您使用示例代码,因为您实际上已经揭示了一个我在这里报告的错误:https://github.com/microsoft/BotBuilder-Samples/issues/2457
这里的潜在问题是对话框库有两种堆叠对话框的方式。通常,一个对话框堆积在另一个对话框的顶部,如下所示:
[ CHOICE_PROMPT ]
[ WATERFALL_DIALOG ]
但是,组件对话框形成一个嵌套的对话框堆栈,该堆栈向内而不是向上堆叠:
[ REVIEW_SELECTION_DIALOG ]
[ TOP_LEVEL_DIALOG ]
[ MAIN_DIALOG ]
由于并非所有对话框都是组件对话框,因此这两种方式组合起来看起来像这样:
[ CHOICE_PROMPT ]
[ WATERFALL_DIALOG ]
[ REVIEW_SELECTION_DIALOG ]
[ TOP_LEVEL_DIALOG ]
[ MAIN_DIALOG ]
我想指出的是,如果您习惯于编写看起来像这样的层次结构列表(最新添加的项目在底部),则堆栈的顺序不一定是您期望的:
有些人可能不考虑堆叠实际堆叠的第二种方法,因为它是父子关系而不是堆叠。之所以称其为第二种堆叠方式,是因为与对话框堆叠在概念上相似。在设计机器人的对话框时,您可以选择是将每个新对话框添加到现有对话框堆栈的顶部还是希望将其作为子级嵌套在内部对话框堆栈中。两种方式的行为类似,因为组件对话框在其最后一个子对话框结束时结束,因此当您从堆栈弹出对话框时,堆栈向外展开的方式与向下展开的方式相同。 (请记住,新对话框已添加到堆栈的顶部,因此此处的“向下”表示从较新的对话框回到较旧的对话框,例如我开始使用的堆栈图。)
“活动对话框”是堆栈顶部的对话框。由于每个组件对话框都有其自己的对话框集,对话框状态以及对话框堆栈和对话框上下文,因此每个组件对话框对于活动对话框是什么都有不同的想法。因为活动对话框是根据特定对话框堆栈定义的,所以当有多个对话框堆栈时,活动对话框取决于您询问的人。
当您在最里面的组件对话框中查找活动对话框时,这对您没有问题。但是随后您用该组件对话框本身替换了该组件对话框的子级。之后,您的(完整)堆栈如下所示:
[ CHOICE_PROMPT ]
[ WATERFALL_DIALOG ]
[ REVIEW_SELECTION_DIALOG ]
[ REVIEW_SELECTION_DIALOG ]
[ TOP_LEVEL_DIALOG ]
[ MAIN_DIALOG ]
当您的CancelAndHelpDialog
尝试访问其内部对话框上下文的活动对话框时,它正确返回了ReviewSelectionDialog
,因为那是其堆栈中唯一的对话框。您想返回选择提示,但该选择提示位于子ReviewSelectionDialog
而不是父ReviewSelectionDialog
的对话框堆栈中。
错误是您应该用自身而不是父组件对话框替换瀑布对话框。所以看起来像这样:
return await stepContext.replaceDialog(WATERFALL_DIALOG, list);
或者像这样:
return await stepContext.replaceDialog(this.initialDialogId, list);
最终,这仍然没有回答您可能要提出的问题。由于您已经看到在中间对话框上下文中获得活动对话框时可能会出现问题,因此您可能需要一种方法来获取“真正的”最里面的活动对话框。这可以通过一些简单的递归来实现:
function getInnermostActiveDialog(dc) {
const child = dc.child;
return child ? getInnermostActiveDialog(child) : dc.activeDialog;
}