Bot 框架 (v4) 使用 HeroCards 在轮播中提示选择,不会进入下一步
我尝试在轮播中使用 HeroCards 和提示选项。因此,用户要选择的选项将显示为 HeroCards。只要用户单击卡片上的按钮,它就会转到下一个瀑布函数。
这是 bot 框架 v3 中的一个工作示例。它确实按预期工作。
const cards = (data || []).map(i => {
return new builder.HeroCard(session)
.title(`${i.productName} ${i.brandName}`)
.subtitle(‘product details’)
.text(‘Choose a product’)
.images([builder.CardImage.create(session, i.image)])
.buttons([builder.CardAction.postBack(session, `${i.id.toString()}`, ‘buy’)]);
});
const msg = new builder.Message(session);
msg.attachmentLayout(builder.AttachmentLayout.carousel);
msg.attachments(cards);
builder.Prompts.choice(session, msg, data.map(i => `${i.id.toString()}`), {
retryPrompt: msg,
});
下面我尝试使用 bot 框架 v4 执行相同操作,但它不起作用。它永远不会进入我的瀑布中的下一个函数。
我如何对 v4 执行相同操作?
…
this.addDialog(new ChoicePrompt(PRODUCTS_CAROUSEL));
…
const productOptions: Partial<Activity> = MessageFactory.carousel(
item.map((p: Product) =>
CardFactory.heroCard(
p.productName,
‘product details’,
[p.image || ''],
[
{
type: ActionTypes.PostBack,
title: ‘buy’,
value: p.id,
},
],
),
),
‘Choose a product’,
);
return await step.prompt(PRODUCTS_CAROUSEL, productOptions);
…
更新:
按照 @Drew Marsh 的建议执行完整代码
export class ProductSelectionDialog extends ComponentDialog {
private selectedProducts: Product[] = [];
private productResult: Product[][];
private stateAccessor: StatePropertyAccessor<State>;
static get Name() {
return PRODUCT_SELECTION_DIALOG;
}
constructor(stateAccessor: StatePropertyAccessor<State>) {
super(PRODUCT_SELECTION_DIALOG);
if (!stateAccessor) {
throw Error('Missing parameter. stateAccessor is required');
}
this.stateAccessor = stateAccessor;
const choicePrompt = new ChoicePrompt(PRODUCTS_CAROUSEL);
choicePrompt.style = ListStyle.none;
this.addDialog(
new WaterfallDialog<State>(REVIEW_PRODUCT_OPTIONS_LOOP, [
this.init.bind(this),
this.selectionStep.bind(this),
this.loopStep.bind(this),
]),
);
this.addDialog(choicePrompt);
}
private init = async (step: WaterfallStepContext<State>) => {
const state = await this.stateAccessor.get(step.context);
if (!this.productResult) this.productResult = state.search.productResult;
return await step.next();
};
private selectionStep = async (step: WaterfallStepContext<State>) => {
const item = this.productResult.shift();
const productOptions: Partial<Activity> = MessageFactory.carousel(
item.map((p: Product) =>
CardFactory.heroCard(
p.productName,
'some text',
[p.image || ''],
[
{
type: ActionTypes.ImBack,
title: 'buy',
value: p.id,
},
],
),
),
'Choose a product',
);
return await step.prompt(PRODUCTS_CAROUSEL, {
prompt: productOptions,
choices: item.map((p: Product) => p.id),
});
};
private loopStep = async (step: WaterfallStepContext<State>) => {
console.log('step.result: ', step.result);
};
}
以下为父级对话框:
…
this.addDialog(new ProductSelectionDialog(stateAccessor));
…
if (search.hasIncompletedProducts) await step.beginDialog(ProductSelectionDialog.Name);
…
return await step.next();
…
我的机器人对话框结构
onTurn()
>>> await this.dialogContext.beginDialog(MainSearchDialog.Name) (LUIS)
>>>>>> await step.beginDialog(QuoteDialog.Name)
>>>>>>>>> await step.beginDialog(ProductSelectionDialog.Name)
更新
用
TextPromt
替换
ChoicePrompt
(如 Kyle Delaney 所建议)似乎会产生相同的结果(不进入下一步),但我意识到如果从提示中删除
return
,如下所示:
return await step.prompt(PRODUCTS_CAROUSEL, `你叫什么名字,人类?`);
TO
await step.prompt(PRODUCTS_CAROUSEL, `你叫什么名字,人类?`);
它确实有效,但是当我返回带有
ChoicePrompt
而不带有
return
的原始代码时,如下所示:
await step.prompt(PRODUCTS_CAROUSEL, {
prompt: productOptions,
choices: item.map((p: Product) => p.id),
});
我得到了另一个框架中的错误:
error: TypeError: Cannot read property 'length' of undefined
at values.sort (/xxx/Workspace/temp/13.basic-bot/node_modules/botbuilder-dialogs/lib/choices/findValues.js:84:48)
at Array.sort (native)
at Object.findValues (/xxx/Workspace/temp/13.basic-bot/node_modules/botbuilder-dialogs/lib/choices/findValues.js:84:25)
at Object.findChoices (/xxx/Workspace/temp/13.basic-bot/node_modules/botbuilder-dialogs/lib/choices/findChoices.js:58:25)
at Object.recognizeChoices (/xxx/Workspace/temp/13.basic-bot/node_modules/botbuilder-dialogs/lib/choices/recognizeChoices.js:75:33)
at ChoicePrompt.<anonymous> (/xxx/Workspace/temp/13.basic-bot/node_modules/botbuilder-dialogs/lib/prompts/choicePrompt.js:62:39)
at Generator.next (<anonymous>)
at /xxx/Workspace/temp/13.basic-bot/node_modules/botbuilder-dialogs/lib/prompts/choicePrompt.js:7:71
at new Promise (<anonymous>)
at __awaiter (/xxx/Workspace/temp/13.basic-bot/node_modules/botbuilder-dialogs/lib/prompts/choicePrompt.js:3:12)
这是行:
// Sort values in descending order by length so that the longest value is searched over first.
const list = values.sort((a, b) => b.value.length - a.value.length);
我可以看到来自我的州的数据正在正常传输 提示:<-- 数据正常 选择:<-- 数据也正常
有时我也会收到此错误:
error: TypeError: Cannot read property 'status' of undefined
at ProductSelectionDialog.<anonymous> (/xxxx/Workspace/temp/13.basic-bot/node_modules/botbuilder-dialogs/lib/componentDialog.js:92:28)
at Generator.next (<anonymous>)
at fulfilled (/xxxx/Workspace/temp/13.basic-bot/node_modules/botbuilder-dialogs/lib/componentDialog.js:4:58)
at <anonymous>
at process._tickDomainCallback (internal/process/next_tick.js:228:7)
此行
// Check for end of inner dialog
if (turnResult.status !== dialog_1.DialogTurnStatus.waiting) {
您正在使用
ChoicePrompt
,但是当您调用
prompt
时,您只会通过一个
activity
(轮播)。
ChoicePrompt
将尝试根据您在调用
prompt
时应该传递的一组选项来验证输入。由于您没有这样做,提示无法将回发值识别为有效值,从技术上讲,应该再次使用轮播提示您做出有效选择。
此处的修复方法是使用
PromptOptions
而不是原始
Activity
调用提示,并将
PromptOptions
的
choices
设置为包含所有您期望返回的值的数组(例如,您为回发按钮的
value
设置的值相同)。
这最终应该看起来有点像这样:
由于您正在使用卡片提供选择 UX,因此您需要将
ChoicePrompt
上的
ListStyle
设置为
none
const productsPrompt = new ChoicePrompt(PRODUCTS_CAROUSEL);
productsPrompt.style = ListStyle.none;
this.addDialog(productsPrompt);
然后,设置可用的
choices
针对特定提示:
return await step.prompt(PRODUCTS_CAROUSEL, {
prompt: productOptions,
choices: items.map((p: Product) => p.id),
});
基本上,Drew Marsh 是对的。
我只想添加一些其他细节,我必须调整这些细节才能使其正常工作。以防其他人像我一样发疯。它可以提供一些见解。这完全取决于您如何处理嵌套对话框的返回。
第一个更改。我必须将 Choice 提示的标识符转换为字符串:
{
type: ActionTypes.PostBack,
title: 'buy',
value: p.id.toString(),
},
和
return await step.prompt(PRODUCTS_CAROUSEL, {
prompt: productOptions,
choices: item.map((p: Product) => p.id.toString()),
});
我发现的另一个问题是在父对话框中:
我基本上是想这样做:
if (search.hasIncompletedProducts) await step.beginDialog(ProductSelectionDialog.Name);
return await step.next();
这没有意义,然后我将其更改为:
if (search.hasIncompletedProducts) {
return await step.beginDialog(ProductSelectionDialog.Name);
} else {
return await step.next();
}
然后是父对话框的父级中的最终更改:
之前是这样的:
switch (step.result) {
case ESearchOptions.OPT1:
await step.beginDialog(OPT1Dialog.Name);
break;
default:
break;
}
await step.endDialog();
这又没有意义,因为我应该返回 beginDialog 或 endDialog。它被更改为:
switch (step.result) {
case ESearchOptions.OPT1:
return await step.beginDialog(OPT1Dialog.Name);
default:
break;
}