如何将对话框与c#中的Bot框架v4中的其他命令相结合?



我是Bot框架v4的新手,我试图编写一个有几个命令的Bot。假设它们是help,ordersdetails

help—检索包含可用命令的消息。
orders-检索一张带有数据表的卡片。
details-开始一个瀑布对话框,检索相同的数据表,请求订单#,然后检索其详细信息。

我面临的问题是OnMessageActivityAsync中的代码在对话框的第二个回合上运行。因此,用户发送details命令,对话框开始,返回订单列表。然后用户发送一个订单号,但是OnMessageActivityAsync运行并且switch语句命中default块中的代码。

我该如何解决这个问题?我试图找出一种方法来检查一个对话框是否正在进行中,然后运行_dialog.RunAsync(...方法,如果它是。

我四处寻找答案,但没有找到任何有效的方法。我遵循微软的指南来制作这个机器人,比如这个关于对话的指南。我还找到了bot示例,其中包括一个具有多个命令的bot和一个具有对话框的bot。然而,我还没有找到一个有效的方法来统一它们。

我的代码看起来像这样…

TroyBot.cs

public class TroyBot<T> : ActivityHandler where T : Dialog
{
private readonly ConversationState _conversationState;
private readonly UserState _userState;
private readonly T _dialog;
public TroyBot(ConversationState conversationState, UserState userState, T dialog)
{
_conversationState = conversationState;
_userState = userState;
_dialog = dialog;
}
protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
foreach (var member in membersAdded)
{
if (member.Id != turnContext.Activity.Recipient.Id)
{
await turnContext.SendActivityAsync(MessageFactory.Text(
"Hi! I'm Troy. I will help you track and manage orders." +
"nn" +
"Say **`help`** to see what I can do."
), cancellationToken);
}
}
}
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
var command = turnContext.Activity.Text.Trim().ToLower();
switch (command)
{
case "orders":
var orders = await GetOrders();
Attachment ordersAttachment = GetOrdersAttachment(orders);
await turnContext.SendActivityAsync(MessageFactory.Attachment(ordersAttachment), cancellationToken);
break;
case "help":
var helpText = GetHelpText();
await turnContext.SendActivityAsync(MessageFactory.Text(helpText), cancellationToken);
break;
case "details":
var detailOrders = await GetOrders();
Attachment detailOrdersAttachment = GetOrdersAttachment(detailOrders);
await turnContext.SendActivityAsync(MessageFactory.Attachment(detailOrdersAttachment), cancellationToken);
await _dialog.RunAsync(turnContext, _conversationState.CreateProperty<DialogState>(nameof(DialogState)), cancellationToken);;
break;
default:
await turnContext.SendActivityAsync(MessageFactory.Text(
"Hmmm... I don't know how to help you with that.nn" +
"Try saying `help` to see what I can do."
), cancellationToken);
break;
}
}
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default)
{
await base.OnTurnAsync(turnContext, cancellationToken);
// Save any state changes that might have occurred during the turn.
await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
await _userState.SaveChangesAsync(turnContext, false, cancellationToken);
}
...
}

OrderDetailDialog.cs

public class OrderDetailDialog : ComponentDialog
{
private readonly IOrderRepo _orderRepo;
public OrderDetailDialog(UserState userState, IOrderRepo orderRepo)
: base(nameof(OrderDetailDialog))
{
_orderRepo = orderRepo;
var waterfallSteps = new WaterfallStep[]
{
OrdersStep,
ViewOrderDetailsStep
};
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), waterfallSteps));
AddDialog(new NumberPrompt<int>(nameof(NumberPrompt<int>), OrderNumberPromptValidatorAsync));
// The initial child Dialog to run.
InitialDialogId = nameof(WaterfallDialog);
}
private static async Task<DialogTurnResult> OrdersStep(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
return await stepContext.PromptAsync(nameof(NumberPrompt<int>), new PromptOptions
{
Prompt = MessageFactory.Text("Please enter the order number."),
RetryPrompt = MessageFactory.Text("The number must be from the list.")
}, cancellationToken);
}
private static async Task<DialogTurnResult> ViewOrderDetailsStep(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
stepContext.Values["orderNumber"] = ((FoundChoice)stepContext.Result).Value;
return await stepContext.EndDialogAsync(GetOrderDetailsCard(), cancellationToken);
}
private async Task<bool> OrderNumberPromptValidatorAsync(PromptValidatorContext<int> promptContext, CancellationToken cancellationToken)
{
var orders = await _orderRepo.GetOrders();
return promptContext.Recognized.Succeeded && orders.Select(x => x.Id).Contains(promptContext.Recognized.Value);
}
private static Attachment GetOrderDetailsCard()
{
var card = new AdaptiveCard(new AdaptiveSchemaVersion(1, 0));
card.Body.Add(new AdaptiveTextBlock("order details card attachment"));
Attachment attachment = new Attachment()
{
ContentType = "application/vnd.microsoft.card.adaptive",
Content = card
};
return attachment;
}
}

您遇到的问题是由于您如何使用OnMessageActivityAsync活动处理程序。实际上,处理程序的名称说明了一切:在每条消息上做一些事情。因此,尽管会话流进入一个对话框,发送的每条消息都将通过这个活动处理程序。无论用户在流中的哪个位置,如果它与其他情况之一不匹配,默认的开关情况总是会被触发。

一个更好的设置是利用对话系统,它可以是一个简单的,但不是过于灵活的瀑布对话框或组件对话框。在您的例子中,您选择了组件对话框,这很好,因为您不需要更改太多。

当然,您需要更新Startup.cs文件,以便在ConfiguredServices下包含如下内容:

// The Dialog that will be run by the bot.
services.AddSingleton<OrderDetailDialog >();
// Create the bot as a transient. In this case the ASP Controller is expecting an IBot.
services.AddTransient<IBot, DialogBot<OrderDetailDialog >>();

并且,你的'DialogBot.cs'文件(你可能有不同的命名)看起来像这样:

using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Schema;
using Microsoft.Extensions.Logging;
namespace Microsoft.BotBuilderSamples
{
public class DialogBot<T> : ActivityHandler where T : Dialog 
{
protected readonly Dialog Dialog;
protected readonly BotState ConversationState;
protected readonly BotState UserState;
protected readonly ILogger Logger;
public DialogBot(ConversationState conversationState, UserState userState, T dialog, ILogger<DialogBot<T>> logger)
{
ConversationState = conversationState;
UserState = userState;
Dialog = dialog;
Logger = logger;
}
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default)
{
await base.OnTurnAsync(turnContext, cancellationToken);
// Save any state changes that might have occurred during the turn.
await ConversationState.SaveChangesAsync(turnContext, false, cancellationToken);
await UserState.SaveChangesAsync(turnContext, false, cancellationToken);
}
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
Logger.LogInformation("Running dialog with Message Activity.");
// Run the Dialog with the new message Activity.
await Dialog.RunAsync(turnContext, ConversationState.CreateProperty<DialogState>(nameof(DialogState)), cancellationToken);
}
}
}

虽然我确实很了解Bot框架SDK,但我更倾向于JS而不是c#。因此,请参考此示例,以确保没有任何其他必要的更改。例如,实现对话框状态。

编辑:

我忘记写switch语句了。我不完全确定如何使用orderhelp语句,但是看起来他们只发送一个活动。如果这是真的,它们可以继续驻留在OnMessageActivityAsync活动处理程序中,因为它们最多只执行一个操作。

对于details对话框,您可以创建一个'main'对话框,然后对活动进行过滤。当"details"作为文本输入时,它将开始OrderDetailDialog。虽然它更复杂,但您可以参考13。core-bot示例,了解如何设置"MainDialog.cs"。此外,如果您愿意,您可以将整个switch语句移动到"MainDialog.cs"(进行一些修改)。而且,如果你真的想,你可以移动orderhelp下的每个动作到他们自己的组件对话框,如果你想要更大的一致性。

相关内容