Att samla in information genom att ställa frågor är ett av de viktigaste sätten som en robot interagerar med användare på. Dialogrutebiblioteket innehåller användbara inbyggda funktioner, till exempel promptklasser som gör det enkelt att ställa frågor och validera svaret för att se till att det matchar en viss datatyp eller uppfyller anpassade valideringsregler.
Du kan hantera linjära och mer komplexa konversationsflöden med hjälp av dialogrutebiblioteket. I en linjär interaktion går roboten igenom en fast sekvens med steg och konversationen avslutas. En dialogruta är användbar när roboten behöver samla in information från användaren.
Den här artikeln visar hur du implementerar linjärt konversationsflöde genom att skapa prompter och anropa dem från en vattenfallsdialogruta.
Exempel på hur du skriver egna frågor utan att använda dialogrutebiblioteket finns i artikeln Skapa egna uppmaningar för att samla in användarindata .
Exemplet med flersvängsprompter använder en vattenfallsdialogruta, några frågor och en komponentdialogruta för att skapa en linjär interaktion som ställer en rad frågor till användaren. Koden använder en dialogruta för att gå igenom följande steg:
Slutligen, om de svarade ja, visa den insamlade informationen; Annars kan du tala om för användaren att deras information inte sparas.
Om du vill använda dialogrutor installerar du NuGet-paketet Microsoft.Bot.Builder.Dialogs .
Roboten interagerar med användaren via UserProfileDialog
. När du skapar robotens DialogBot
klass anges den UserProfileDialog
som huvuddialogruta. Roboten använder sedan en Run
hjälpmetod för att komma åt dialogrutan.
Dialogrutor\UserProfileDialog.cs
Börja med att skapa UserProfileDialog
som härleds från ComponentDialog
klassen och har sju steg.
UserProfileDialog
I konstruktorn skapar du vattenfallsstegen, anvisningarna och vattenfallsdialogrutan och lägger till dem i dialogrutan. Prompterna måste finnas i samma dialogruta som de används i.
public UserProfileDialog(UserState userState)
: base(nameof(UserProfileDialog))
{
_userProfileAccessor = userState.CreateProperty<UserProfile>("UserProfile");
// This array defines how the Waterfall will execute.
var waterfallSteps = new WaterfallStep[]
{
TransportStepAsync,
NameStepAsync,
NameConfirmStepAsync,
AgeStepAsync,
PictureStepAsync,
SummaryStepAsync,
ConfirmStepAsync,
};
// Add named dialogs to the DialogSet. These names are saved in the dialog state.
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), waterfallSteps));
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(new NumberPrompt<int>(nameof(NumberPrompt<int>), AgePromptValidatorAsync));
AddDialog(new ChoicePrompt(nameof(ChoicePrompt)));
AddDialog(new ConfirmPrompt(nameof(ConfirmPrompt)));
AddDialog(new AttachmentPrompt(nameof(AttachmentPrompt), PicturePromptValidatorAsync));
// The initial child Dialog to run.
InitialDialogId = nameof(WaterfallDialog);
}
Lägg sedan till de steg som används i dialogrutan för att fråga efter indata. Om du vill använda en prompt anropar du den från ett steg i dialogrutan och hämtar promptresultatet i följande steg med hjälp av stepContext.Result
. I bakgrunden är frågor en dialogruta i två steg. Först frågar uppmaningen efter indata. Sedan returneras det giltiga värdet, eller börjar om från början med en reprompt tills det tar emot en giltig indata.
Du bör alltid returnera en icke-null DialogTurnResult
från ett vattenfallssteg. Om du inte gör det kanske dialogrutan inte fungerar som den är utformad. Nedan visas implementeringen för NameStepAsync
i vattenfallsdialogrutan.
private static async Task<DialogTurnResult> NameStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
stepContext.Values["transport"] = ((FoundChoice)stepContext.Result).Value;
return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = MessageFactory.Text("Please enter your name.") }, cancellationToken);
}
I AgeStepAsync
anger du en återförsöksprompt för när användarens indata inte kan verifieras, antingen för att det är i ett format som prompten inte kan parsa, eller så misslyckas indata med ett valideringsvillkor. I det här fallet, om ingen återförsöksprompt angavs, använder prompten den första prompttexten för att skicka om användaren för indata.
private async Task<DialogTurnResult> AgeStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
if ((bool)stepContext.Result)
{
// User said "yes" so we will be prompting for the age.
// WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
var promptOptions = new PromptOptions
{
Prompt = MessageFactory.Text("Please enter your age."),
RetryPrompt = MessageFactory.Text("The value entered must be greater than 0 and less than 150."),
};
return await stepContext.PromptAsync(nameof(NumberPrompt<int>), promptOptions, cancellationToken);
}
else
{
// User said "no" so we will skip the next step. Give -1 as the age.
return await stepContext.NextAsync(-1, cancellationToken);
}
}
UserProfile.cs
Användarens transportsätt, namn och ålder sparas i en instans av UserProfile
klassen.
public class UserProfile
{
public string Transport { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public Attachment Picture { get; set; }
}
Dialogrutor\UserProfileDialog.cs
I det sista steget kontrollerar du den stepContext.Result
som returnerades av dialogrutan som anropades i föregående vattenfallssteg. Om returvärdet är sant hämtas användarprofilens accessor och användarprofilen uppdateras. Om du vill hämta användarprofilen anropar GetAsync
du och anger sedan värdena för userProfile.Transport
egenskaperna , userProfile.Name
userProfile.Age
och userProfile.Picture
. Sammanfatta slutligen informationen för användaren innan du anropar EndDialogAsync
, vilket avslutar dialogrutan. När dialogrutan avslutas visas den från dialogstacken och returnerar ett valfritt resultat till dialogrutans överordnade. Den överordnade är den dialogruta eller metod som startade dialogrutan som precis avslutades.
else
{
msg += $" Your profile will not be kept.";
}
await stepContext.Context.SendActivityAsync(MessageFactory.Text(msg), cancellationToken);
// WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is the end.
return await stepContext.EndDialogAsync(cancellationToken: cancellationToken);
}
private async Task<DialogTurnResult> SummaryStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
stepContext.Values["picture"] = ((IList<Attachment>)stepContext.Result)?.FirstOrDefault();
// Get the current profile object from user state.
var userProfile = await _userProfileAccessor.GetAsync(stepContext.Context, () => new UserProfile(), cancellationToken);
userProfile.Transport = (string)stepContext.Values["transport"];
userProfile.Name = (string)stepContext.Values["name"];
userProfile.Age = (int)stepContext.Values["age"];
userProfile.Picture = (Attachment)stepContext.Values["picture"];
var msg = $"I have your mode of transport as {userProfile.Transport} and your name as {userProfile.Name}";
if (userProfile.Age != -1)
{
msg += $" and your age as {userProfile.Age}";
}
msg += ".";
await stepContext.Context.SendActivityAsync(MessageFactory.Text(msg), cancellationToken);
if (userProfile.Picture != null)
{
try
{
await stepContext.Context.SendActivityAsync(MessageFactory.Attachment(userProfile.Picture, "This is your profile picture."), cancellationToken);
}
catch
{
await stepContext.Context.SendActivityAsync(MessageFactory.Text("A profile picture was saved but could not be displayed here."), cancellationToken);
Om du vill använda dialogrutor måste projektet installera npm-paketet botbuilder-dialogs .
Roboten interagerar med användaren via en UserProfileDialog
. När du skapar robotens DialogBot
UserProfileDialog
anges den som huvuddialogruta. Roboten använder sedan en run
hjälpmetod för att komma åt dialogrutan.
dialogrutor/userProfileDialog.js
Börja med att skapa UserProfileDialog
som härleds från ComponentDialog
klassen och har sju steg.
UserProfileDialog
I konstruktorn skapar du vattenfallsstegen, anvisningarna och vattenfallsdialogrutan och lägger till dem i dialogrutan. Prompterna måste finnas i samma dialogruta som de används i.
constructor(userState) {
super('userProfileDialog');
this.userProfile = userState.createProperty(USER_PROFILE);
this.addDialog(new TextPrompt(NAME_PROMPT));
this.addDialog(new ChoicePrompt(CHOICE_PROMPT));
this.addDialog(new ConfirmPrompt(CONFIRM_PROMPT));
this.addDialog(new NumberPrompt(NUMBER_PROMPT, this.agePromptValidator));
this.addDialog(new AttachmentPrompt(ATTACHMENT_PROMPT, this.picturePromptValidator));
this.addDialog(new WaterfallDialog(WATERFALL_DIALOG, [
this.transportStep.bind(this),
this.nameStep.bind(this),
this.nameConfirmStep.bind(this),
this.ageStep.bind(this),
this.pictureStep.bind(this),
this.summaryStep.bind(this),
this.confirmStep.bind(this)
]));
this.initialDialogId = WATERFALL_DIALOG;
}
Lägg sedan till de steg som används i dialogrutan för att fråga efter indata. Om du vill använda en uppmaning anropar du den från ett steg i dialogrutan och hämtar promptresultatet i följande steg från stegkontexten, i det här fallet med hjälp step.result
av . I bakgrunden är frågor en dialogruta i två steg. Först frågar uppmaningen efter indata. Sedan returneras det giltiga värdet, eller börjar om från början med en reprompt tills det tar emot en giltig indata.
Du bör alltid returnera en icke-null DialogTurnResult
från ett vattenfallssteg. Om du inte gör det kanske dialogrutan inte fungerar som den är utformad. Nedan visas implementeringen för nameStep
i vattenfallsdialogrutan.
async nameStep(step) {
step.values.transport = step.result.value;
return await step.prompt(NAME_PROMPT, 'Please enter your name.');
}
I ageStep
anger du en återförsöksprompt för när användarens indata inte kan verifieras, antingen för att det är i ett format som prompten inte kan parsa eller att indata misslyckas med ett valideringsvillkor, som anges i konstruktorn ovan. I det här fallet, om ingen återförsöksprompt angavs, använder prompten den första prompttexten för att skicka om användaren för indata.
async ageStep(step) {
if (step.result) {
// User said "yes" so we will be prompting for the age.
// WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
const promptOptions = { prompt: 'Please enter your age.', retryPrompt: 'The value entered must be greater than 0 and less than 150.' };
return await step.prompt(NUMBER_PROMPT, promptOptions);
} else {
// User said "no" so we will skip the next step. Give -1 as the age.
return await step.next(-1);
}
}
userProfile.js
Användarens transportsätt, namn och ålder sparas i en instans av UserProfile
klassen.
class UserProfile {
constructor(transport, name, age, picture) {
this.transport = transport;
this.name = name;
this.age = age;
this.picture = picture;
}
}
dialogrutor/userProfileDialog.js
I det sista steget kontrollerar du den step.result
som returnerades av dialogrutan som anropades i föregående vattenfallssteg. Om returvärdet är sant hämtas användarprofilens accessor och användarprofilen uppdateras. Om du vill hämta användarprofilen anropar get
du och anger sedan värdena för userProfile.transport
egenskaperna , userProfile.age
userProfile.name
och userProfile.picture
. Sammanfatta slutligen informationen för användaren innan du anropar endDialog
, vilket avslutar dialogrutan. När dialogrutan avslutas visas den från dialogstacken och returnerar ett valfritt resultat till dialogrutans överordnade. Den överordnade är den dialogruta eller metod som startade dialogrutan som precis avslutades.
await step.context.sendActivity(msg);
// WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
return await step.endDialog();
}
async summaryStep(step) {
step.values.picture = step.result && step.result[0];
// Get the current profile object from user state.
const userProfile = await this.userProfile.get(step.context, new UserProfile());
userProfile.transport = step.values.transport;
userProfile.name = step.values.name;
userProfile.age = step.values.age;
userProfile.picture = step.values.picture;
let msg = `I have your mode of transport as ${ userProfile.transport } and your name as ${ userProfile.name }`;
if (userProfile.age !== -1) {
msg += ` and your age as ${ userProfile.age }`;
}
msg += '.';
await step.context.sendActivity(msg);
if (userProfile.picture) {
try {
await step.context.sendActivity(MessageFactory.attachment(userProfile.picture, 'This is your profile picture.'));
} catch {
await step.context.sendActivity('A profile picture was saved but could not be displayed here.');
}
Skapa tilläggsmetoden för att köra vattenfallsdialogrutan
En run
hjälpmetod, definierad i userProfileDialog
, används för att skapa och komma åt dialogkontexten. accessor
Här är tillståndsegenskapsåtkomsten för egenskapen dialogtillstånd och this
dialogrutan komponent för användarprofil. Eftersom komponentdialogrutor definierar en inre dialogruta måste en yttre dialoguppsättning skapas som är synlig för meddelandehanterarkoden och som används för att skapa en dialogkontext.
Dialogkontexten createContext
skapas genom att anropa metoden och används för att interagera med dialogrutan inifrån robotens turhanterare. Dialogkontexten innehåller den aktuella turkontexten, den överordnade dialogrutan och dialogtillståndet, som tillhandahåller en metod för att bevara information i dialogrutan.
Med dialogkontexten kan du starta en dialogruta med sträng-ID:t eller fortsätta den aktuella dialogrutan (till exempel en vattenfallsdialogruta med flera steg). Dialogkontexten skickas vidare till alla robotens dialogrutor och vattenfallssteg.
async run(turnContext, accessor) {
const dialogSet = new DialogSet(accessor);
dialogSet.add(this);
const dialogContext = await dialogSet.createContext(turnContext);
const results = await dialogContext.continueDialog();
if (results.status === DialogTurnStatus.empty) {
await dialogContext.beginDialog(this.id);
}
}
Roboten interagerar med användaren via UserProfileDialog
. När du skapar robotens DialogBot
klass anges den UserProfileDialog
som huvuddialogruta. Roboten använder sedan en Run
hjälpmetod för att komma åt dialogrutan.
UserProfileDialog.java
Börja med att skapa UserProfileDialog
som härleds från ComponentDialog
klassen och har sju steg.
UserProfileDialog
I konstruktorn skapar du vattenfallsstegen, anvisningarna och vattenfallsdialogrutan och lägger till dem i dialogrutan. Prompterna måste finnas i samma dialogruta som de används i.
public UserProfileDialog(UserState withUserState) {
super("UserProfileDialog");
userProfileAccessor = withUserState.createProperty("UserProfile");
WaterfallStep[] waterfallSteps = {
UserProfileDialog::transportStep,
UserProfileDialog::nameStep,
this::nameConfirmStep,
this::ageStep,
UserProfileDialog::pictureStep,
this::confirmStep,
this::summaryStep
};
// Add named dialogs to the DialogSet. These names are saved in the dialog state.
addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps)));
addDialog(new TextPrompt("TextPrompt"));
addDialog(new NumberPrompt<Integer>("NumberPrompt", UserProfileDialog::agePromptValidator, Integer.class));
addDialog(new ChoicePrompt("ChoicePrompt"));
addDialog(new ConfirmPrompt("ConfirmPrompt"));
addDialog(new AttachmentPrompt("AttachmentPrompt", UserProfileDialog::picturePromptValidator));
// The initial child Dialog to run.
setInitialDialogId("WaterfallDialog");
}
Lägg sedan till de steg som används i dialogrutan för att fråga efter indata. Om du vill använda en prompt anropar du den från ett steg i dialogrutan och hämtar promptresultatet i följande steg med hjälp av stepContext.getResult()
. I bakgrunden är frågor en dialogruta i två steg. Först frågar uppmaningen efter indata. Sedan returneras det giltiga värdet, eller börjar om från början med en reprompt tills det tar emot en giltig indata.
Du bör alltid returnera en icke-null DialogTurnResult
från ett vattenfallssteg. Om du inte gör det kanske dialogrutan inte fungerar som den är utformad. Nedan visas implementeringen för nameStep
i vattenfallsdialogrutan.
private static CompletableFuture<DialogTurnResult> nameStep(WaterfallStepContext stepContext) {
stepContext.getValues().put("transport", ((FoundChoice) stepContext.getResult()).getValue());
PromptOptions promptOptions = new PromptOptions();
promptOptions.setPrompt(MessageFactory.text("Please enter your name."));
return stepContext.prompt("TextPrompt", promptOptions);
}
I ageStep
anger du en återförsöksprompt för när användarens indata inte kan verifieras, antingen för att det är i ett format som prompten inte kan parsa, eller så misslyckas indata med ett valideringsvillkor. I det här fallet, om ingen återförsöksprompt angavs, använder prompten den första prompttexten för att skicka om användaren för indata.
private CompletableFuture<DialogTurnResult> ageStep(WaterfallStepContext stepContext) {
if ((Boolean)stepContext.getResult()) {
// User said "yes" so we will be prompting for the age.
// WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
PromptOptions promptOptions = new PromptOptions();
promptOptions.setPrompt(MessageFactory.text("Please enter your age."));
promptOptions.setRetryPrompt(MessageFactory.text("The value entered must be greater than 0 and less than 150."));
return stepContext.prompt("NumberPrompt", promptOptions);
}
// User said "no" so we will skip the next step. Give -1 as the age.
return stepContext.next(-1);
}
UserProfile.java
Användarens transportsätt, namn och ålder sparas i en instans av UserProfile
klassen.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.sample.multiturnprompt;
import com.microsoft.bot.schema.Attachment;
/**
* This is our application state.
*/
public class UserProfile {
public String transport;
public String name;
public Integer age;
public Attachment picture;
}
UserProfileDialog.java
I det sista steget kontrollerar du den stepContext.Result
som returnerades av dialogrutan som anropades i föregående vattenfallssteg. Om returvärdet är sant hämtas användarprofilens accessor och användarprofilen uppdateras. Om du vill hämta användarprofilen anropar get
du och anger sedan värdena för userProfile.Transport
egenskaperna , userProfile.Name
userProfile.Age
och userProfile.Picture
. Sammanfatta slutligen informationen för användaren innan du anropar endDialog
, vilket avslutar dialogrutan. När dialogrutan avslutas visas den från dialogstacken och returnerar ett valfritt resultat till dialogrutans överordnade. Den överordnade är den dialogruta eller metod som startade dialogrutan som precis avslutades.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.sample.multiturnprompt;
import com.microsoft.bot.builder.MessageFactory;
import com.microsoft.bot.builder.StatePropertyAccessor;
import com.microsoft.bot.builder.UserState;
import com.microsoft.bot.connector.Channels;
import com.microsoft.bot.dialogs.ComponentDialog;
import com.microsoft.bot.dialogs.DialogTurnResult;
import com.microsoft.bot.dialogs.WaterfallDialog;
import com.microsoft.bot.dialogs.WaterfallStep;
import com.microsoft.bot.dialogs.WaterfallStepContext;
import com.microsoft.bot.dialogs.choices.ChoiceFactory;
import com.microsoft.bot.dialogs.choices.FoundChoice;
import com.microsoft.bot.dialogs.prompts.AttachmentPrompt;
import com.microsoft.bot.dialogs.prompts.ChoicePrompt;
import com.microsoft.bot.dialogs.prompts.ConfirmPrompt;
import com.microsoft.bot.dialogs.prompts.NumberPrompt;
import com.microsoft.bot.dialogs.prompts.PromptOptions;
import com.microsoft.bot.dialogs.prompts.PromptValidatorContext;
import com.microsoft.bot.dialogs.prompts.TextPrompt;
import com.microsoft.bot.schema.Attachment;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.apache.commons.lang3.StringUtils;
public class UserProfileDialog extends ComponentDialog {
private final StatePropertyAccessor<UserProfile> userProfileAccessor;
public UserProfileDialog(UserState withUserState) {
super("UserProfileDialog");
userProfileAccessor = withUserState.createProperty("UserProfile");
WaterfallStep[] waterfallSteps = {
UserProfileDialog::transportStep,
UserProfileDialog::nameStep,
this::nameConfirmStep,
this::ageStep,
UserProfileDialog::pictureStep,
this::confirmStep,
this::summaryStep
};
// Add named dialogs to the DialogSet. These names are saved in the dialog state.
addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps)));
addDialog(new TextPrompt("TextPrompt"));
addDialog(new NumberPrompt<Integer>("NumberPrompt", UserProfileDialog::agePromptValidator, Integer.class));
addDialog(new ChoicePrompt("ChoicePrompt"));
addDialog(new ConfirmPrompt("ConfirmPrompt"));
addDialog(new AttachmentPrompt("AttachmentPrompt", UserProfileDialog::picturePromptValidator));
// The initial child Dialog to run.
setInitialDialogId("WaterfallDialog");
}
private static CompletableFuture<DialogTurnResult> transportStep(WaterfallStepContext stepContext) {
// WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
// Running a prompt here means the next WaterfallStep will be run when the user's response is received.
PromptOptions promptOptions = new PromptOptions();
promptOptions.setPrompt(MessageFactory.text("Please enter your mode of transport."));
promptOptions.setChoices(ChoiceFactory.toChoices("Car", "Bus", "Bicycle"));
return stepContext.prompt("ChoicePrompt", promptOptions);
}
private static CompletableFuture<DialogTurnResult> nameStep(WaterfallStepContext stepContext) {
stepContext.getValues().put("transport", ((FoundChoice) stepContext.getResult()).getValue());
PromptOptions promptOptions = new PromptOptions();
promptOptions.setPrompt(MessageFactory.text("Please enter your name."));
return stepContext.prompt("TextPrompt", promptOptions);
}
private CompletableFuture<DialogTurnResult> nameConfirmStep(WaterfallStepContext stepContext) {
stepContext.getValues().put("name", stepContext.getResult());
// We can send messages to the user at any point in the WaterfallStep.
return stepContext.getContext().sendActivity(MessageFactory.text(String.format("Thanks %s.", stepContext.getResult())))
.thenCompose(resourceResponse -> {
// WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
PromptOptions promptOptions = new PromptOptions();
promptOptions.setPrompt(MessageFactory.text("Would you like to give your age?"));
return stepContext.prompt("ConfirmPrompt", promptOptions);
});
}
private CompletableFuture<DialogTurnResult> ageStep(WaterfallStepContext stepContext) {
if ((Boolean)stepContext.getResult()) {
// User said "yes" so we will be prompting for the age.
// WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
PromptOptions promptOptions = new PromptOptions();
promptOptions.setPrompt(MessageFactory.text("Please enter your age."));
promptOptions.setRetryPrompt(MessageFactory.text("The value entered must be greater than 0 and less than 150."));
return stepContext.prompt("NumberPrompt", promptOptions);
}
// User said "no" so we will skip the next step. Give -1 as the age.
return stepContext.next(-1);
}
private static CompletableFuture<DialogTurnResult> pictureStep(WaterfallStepContext stepContext) {
stepContext.getValues().put("age", (Integer) stepContext.getResult());
String msg = (Integer)stepContext.getValues().get("age") == -1
? "No age given."
: String.format("I have your age as %d.", (Integer)stepContext.getValues().get("age"));
// We can send messages to the user at any point in the WaterfallStep.
return stepContext.getContext().sendActivity(MessageFactory.text(msg))
.thenCompose(resourceResponse -> {
if (StringUtils.equals(stepContext.getContext().getActivity().getChannelId(), Channels.MSTEAMS)) {
// This attachment prompt example is not designed to work for Teams attachments, so skip it in this case
return stepContext.getContext().sendActivity(MessageFactory.text("Skipping attachment prompt in Teams channel..."))
.thenCompose(resourceResponse1 -> stepContext.next(null));
}
// WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
PromptOptions promptOptions = new PromptOptions();
promptOptions.setPrompt(MessageFactory.text("Please attach a profile picture (or type any message to skip)."));
promptOptions.setRetryPrompt(MessageFactory.text("The attachment must be a jpeg/png image file."));
return stepContext.prompt("AttachmentPrompt", promptOptions);
});
}
private CompletableFuture<DialogTurnResult> confirmStep(WaterfallStepContext stepContext) {
List<Attachment> attachments = (List<Attachment>)stepContext.getResult();
stepContext.getValues().put("picture", attachments == null ? null : attachments.get(0));
// WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
PromptOptions promptOptions = new PromptOptions();
promptOptions.setPrompt(MessageFactory.text("Is this ok?"));
return stepContext.prompt("ConfirmPrompt", promptOptions);
}
private CompletableFuture<DialogTurnResult> summaryStep(WaterfallStepContext stepContext) {
if ((Boolean)stepContext.getResult()) {
// Get the current profile object from user state.
return userProfileAccessor.get(stepContext.getContext(), () -> new UserProfile())
.thenCompose(userProfile -> {
userProfile.transport = (String) stepContext.getValues().get("transport");
userProfile.name = (String) stepContext.getValues().get("name");
userProfile.age = (Integer) stepContext.getValues().get("age");
userProfile.picture = (Attachment) stepContext.getValues().get("picture");
String msg = String.format(
"I have your mode of transport as %s and your name as %s",
userProfile.transport, userProfile.name
);
if (userProfile.age != -1) {
msg += String.format(" and your age as %s", userProfile.age);
}
msg += ".";
return stepContext.getContext().sendActivity(MessageFactory.text(msg))
.thenApply(resourceResponse -> userProfile);
})
.thenCompose(userProfile -> {
if (userProfile.picture != null) {
try {
return stepContext.getContext().sendActivity(
MessageFactory.attachment(userProfile.picture,
"This is your profile picture."
));
} catch(Exception ex) {
return stepContext.getContext().sendActivity(
MessageFactory.text(
"A profile picture was saved but could not be displayed here."
));
}
}
return stepContext.getContext().sendActivity(
MessageFactory.text("A profile picture wasn't attached.")
);
})
.thenCompose(resourceResponse -> stepContext.endDialog());
}
// WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is the end.
return stepContext.getContext().sendActivity(MessageFactory.text("Thanks. Your profile will not be kept."))
.thenCompose(resourceResponse -> stepContext.endDialog());
}
private static CompletableFuture<Boolean> agePromptValidator(
PromptValidatorContext<Integer> promptContext
) {
// This condition is our validation rule. You can also change the value at this point.
return CompletableFuture.completedFuture(
promptContext.getRecognized().getSucceeded()
&& promptContext.getRecognized().getValue() > 0
&& promptContext.getRecognized().getValue() < 150);
}
private static CompletableFuture<Boolean> picturePromptValidator(
PromptValidatorContext<List<Attachment>> promptContext
) {
if (promptContext.getRecognized().getSucceeded()) {
List<Attachment> attachments = promptContext.getRecognized().getValue();
List<Attachment> validImages = new ArrayList<>();
for (Attachment attachment : attachments) {
if (StringUtils.equals(
attachment.getContentType(), "image/jpeg") || StringUtils.equals(attachment.getContentType(), "image/png")
) {
validImages.add(attachment);
}
}
promptContext.getRecognized().setValue(validImages);
// If none of the attachments are valid images, the retry prompt should be sent.
return CompletableFuture.completedFuture(!validImages.isEmpty());
}
else {
// We can return true from a validator function even if Recognized.Succeeded is false.
return promptContext.getContext().sendActivity("No attachments received. Proceeding without a profile picture...")
.thenApply(resourceResponse -> true);
}
}
}
Om du vill använda dialogrutor installerar du botbuilder-dialogrutor och botbuilder-ai PyPI-paket genom att köra pip install botbuilder-dialogs
och pip install botbuilder-ai
från en terminal.
Roboten interagerar med användaren via UserProfileDialog
. När robotens DialogBot
klass skapas anges den UserProfileDialog
som huvuddialogruta. Roboten använder sedan en run_dialog
hjälpmetod för att komma åt dialogrutan.
dialogrutor\user_profile_dialog.py
Börja med att skapa UserProfileDialog
som härleds från ComponentDialog
klassen och har sju steg.
UserProfileDialog
I konstruktorn skapar du vattenfallsstegen, anvisningarna och vattenfallsdialogrutan och lägger till dem i dialogrutan. Prompterna måste finnas i samma dialogruta som de används i.
def __init__(self, user_state: UserState):
super(UserProfileDialog, self).__init__(UserProfileDialog.__name__)
self.user_profile_accessor = user_state.create_property("UserProfile")
self.add_dialog(
WaterfallDialog(
WaterfallDialog.__name__,
[
self.transport_step,
self.name_step,
self.name_confirm_step,
self.age_step,
self.picture_step,
self.summary_step,
self.confirm_step,
],
)
)
self.add_dialog(TextPrompt(TextPrompt.__name__))
self.add_dialog(
NumberPrompt(NumberPrompt.__name__, UserProfileDialog.age_prompt_validator)
)
self.add_dialog(ChoicePrompt(ChoicePrompt.__name__))
self.add_dialog(ConfirmPrompt(ConfirmPrompt.__name__))
self.add_dialog(
AttachmentPrompt(
AttachmentPrompt.__name__, UserProfileDialog.picture_prompt_validator
)
)
self.initial_dialog_id = WaterfallDialog.__name__
Lägg sedan till de steg som används i dialogrutan för att fråga efter indata. Om du vill använda en prompt anropar du den från ett steg i dialogrutan och hämtar promptresultatet i följande steg med hjälp av step_context.result
. I bakgrunden är frågor en dialogruta i två steg. Först frågar uppmaningen efter indata. Sedan returneras det giltiga värdet, eller börjar om från början med en reprompt tills det tar emot en giltig indata.
Du bör alltid returnera en icke-null DialogTurnResult
från ett vattenfallssteg. Om du inte gör det kanske dialogrutan inte fungerar som den är utformad. Här kan du se implementeringen för name_step
i vattenfallsdialogrutan.
async def name_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
step_context.values["transport"] = step_context.result.value
return await step_context.prompt(
TextPrompt.__name__,
PromptOptions(prompt=MessageFactory.text("Please enter your name.")),
)
I age_step
anger du en återförsöksprompt för när användarens indata inte kan verifieras, antingen för att det är i ett format som prompten inte kan parsa eller att indata misslyckas med ett valideringsvillkor, som anges i konstruktorn ovan. I det här fallet, om ingen återförsöksprompt har angetts, använder prompten den första prompttexten för att skicka om användaren för indata
async def age_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
if step_context.result:
# User said "yes" so we will be prompting for the age.
# WaterfallStep always finishes with the end of the Waterfall or with another dialog,
# here it is a Prompt Dialog.
return await step_context.prompt(
NumberPrompt.__name__,
PromptOptions(
prompt=MessageFactory.text("Please enter your age."),
retry_prompt=MessageFactory.text(
"The value entered must be greater than 0 and less than 150."
),
),
)
# User said "no" so we will skip the next step. Give -1 as the age.
return await step_context.next(-1)
data_models\user_profile.py
Användarens transportsätt, namn och ålder sparas i en instans av UserProfile
klassen.
class UserProfile:
"""
This is our application state. Just a regular serializable Python class.
"""
def __init__(self, name: str = None, transport: str = None, age: int = 0, picture: Attachment = None):
self.name = name
self.transport = transport
self.age = age
self.picture = picture
dialogrutor\user_profile_dialog.py
I det sista steget kontrollerar du den step_context.result
som returnerades av dialogrutan som anropades i föregående vattenfallssteg. Om returvärdet är sant hämtas användarprofilens accessor och användarprofilen uppdateras. Om du vill hämta användarprofilen anropar get
du och anger sedan värdena för user_profile.transport
egenskaperna , user_profile.name
och user_profile.age
. Sammanfatta slutligen informationen för användaren innan du anropar end_dialog
, vilket avslutar dialogrutan. När dialogrutan avslutas visas den från dialogstacken och returnerar ett valfritt resultat till dialogrutans överordnade. Den överordnade är den dialogruta eller metod som startade dialogrutan som precis avslutades.
async def summary_step(
self, step_context: WaterfallStepContext
) -> DialogTurnResult:
step_context.values["picture"] = (
None if not step_context.result else step_context.result[0]
)
# Get the current profile object from user state. Changes to it
# will saved during Bot.on_turn.
user_profile = await self.user_profile_accessor.get(
step_context.context, UserProfile
)
user_profile.transport = step_context.values["transport"]
user_profile.name = step_context.values["name"]
user_profile.age = step_context.values["age"]
user_profile.picture = step_context.values["picture"]
msg = f"I have your mode of transport as {user_profile.transport} and your name as {user_profile.name}."
if user_profile.age != -1:
msg += f" And age as {user_profile.age}."
await step_context.context.send_activity(MessageFactory.text(msg))
if user_profile.picture:
await step_context.context.send_activity(
MessageFactory.attachment(
user_profile.picture, "This is your profile picture."
)
)
else:
await step_context.context.send_activity(
"A profile picture was saved but could not be displayed here."
)
# WaterfallStep always finishes with the end of the Waterfall or with another
# dialog, here it is the end.
return await step_context.prompt(
ConfirmPrompt.__name__,
Skapa tilläggsmetoden för att köra vattenfallsdialogrutan
En run_dialog()
hjälpmetod definieras i hjälpen\dialog_helper.py som används för att skapa och komma åt dialogkontexten. accessor
Här är tillståndsegenskapsåtkomsten för egenskapen dialogtillstånd och dialog
dialogrutan komponent för användarprofil. Eftersom komponentdialogrutor definierar en inre dialogruta måste en yttre dialogruta skapas som är synlig för meddelandehanterarkoden och använda den för att skapa en dialogkontext.
Skapa dialogkontexten genom att anropa , create_context
som används för att interagera med dialogrutan från robotens turhanterare. Dialogkontexten innehåller den aktuella turkontexten, den överordnade dialogrutan och dialogtillståndet, som tillhandahåller en metod för att bevara information i dialogrutan.
Med dialogkontexten kan du starta en dialogruta med sträng-ID:t eller fortsätta den aktuella dialogrutan (till exempel en vattenfallsdialogruta som har flera steg). Dialogkontexten skickas vidare till alla robotens dialogrutor och vattenfallssteg.
class DialogHelper:
@staticmethod
async def run_dialog(
dialog: Dialog, turn_context: TurnContext, accessor: StatePropertyAccessor
):
dialog_set = DialogSet(accessor)
dialog_set.add(dialog)
dialog_context = await dialog_set.create_context(turn_context)
results = await dialog_context.continue_dialog()
if results.status == DialogTurnStatus.Empty:
await dialog_context.begin_dialog(dialog.id)
Det här exemplet uppdaterar användarprofilens tillstånd inifrån dialogrutan. Den här metoden kan fungera för vissa robotar, men det fungerar inte om du vill återanvända en dialogruta mellan robotar.
Det finns olika alternativ för att hålla dialogsteg och robottillstånd åtskilda. När dialogrutan till exempel har samlat in fullständig information kan du: