在上一篇([08]改用TextPrompt詢問使用者姓名)使用TextPrompt
來取得使用者的姓名,感覺起來好像和自己維護狀態沒什麽兩樣,因爲還是需要透過if else來呼叫。
這樣Dialogs還有意義嗎?
這篇將會介紹另外一種使用情景,有時候需要搜集使用者的資料,例如説他要訂房的話會需要搜集他要訂什麽時間,住幾個晚上等等,這個時候Dialog就變得更加方便。
這篇將介紹如何透過waterfall
來達到這個效果。
FormFlow
,waterfall dialog做出來有點類似一樣的概念,有興趣可以看看:[07]使用FormFlow讓Chatbot搜集表單資訊更容易想增加什麽功能?
有了使用者的姓名,接下來就是看看如何協助訂房的輸入。
這邊需要幾個資訊:
- 從那天入住
- 要住幾個晚上
- 總共幾個人
- 床的大小
瞭解需求了之後,接下來就是開發。
調整什麽?
接下來就要看看要調整什麽。
首先,需要建立一個POCO的model代表這個訂房的信息。
再來,建立一個dialog用來啓動這個流程。
然後,暫時先把取得姓名那段拿掉。
最後就是測試。
整個拆接下來就是:
- 建立出
RoomReservation
的POCO - 整合剛建立的POCO到
CounterState
- 再來建立出一個新的DialogSet
- 定義每一個step要做的事情
- 修改bot邏輯呼叫DialogSet
- 測試
建立出RoomReservation的POCO
首先第一步是建立出POCO,因此在Model資料夾下面建立:RoomReservation
public class RoomReservation
{
public DateTime StartDate { get; set; }
public int NumberOfNightToStay { get; set; }
public int NumberOfPepole { get; set; }
public string BedSize { get; set; }
public override string ToString()
{
return $"入住日期:{StartDate}{Environment.NewLine}" +
$"入住幾晚:{NumberOfNightToStay}{Environment.NewLine}" +
$"幾人:{NumberOfPepole}{Environment.NewLine}" +
$"床型:{BedSize}{Environment.NewLine}";
}
}
整合剛建立的POCO到CounterState
要把RoomReservation
加入到Accessor,這邊偷懶用CounterState
,因此在增加一個property進去:
public class CounterState
{
...
public RoomReservation RoomReservation { get; set; } = new RoomReservation();
}
再來建立出一個新的DialogSet
再來切換到EchoWithCounterBot.cs
,首先是準備好DialogSet:
public class EchoWithCounterBot : IBot
{
...
private readonly DialogSet _dialogsWaterfall;
public EchoWithCounterBot
(EchoBotAccessors accessors, ILoggerFactory loggerFactory)
{
_dialogsWaterfall = new DialogSet(_accessors.DialogState);
var waterfallSteps = new WaterfallStep[]
{
GetStartStayDateAsync,
GetStayDayAsync,
GetNumberOfOccupantAsync,
GetBedSizeAsync,
GetConfirmAsync,
GetSummaryAsync,
};
_dialogsWaterfall.Add(new WaterfallDialog("formFlow", waterfallSteps));
_dialogsWaterfall.Add(new DateTimePrompt("dateTime"));
_dialogsWaterfall.Add(new NumberPrompt<int>("number"));
_dialogsWaterfall.Add(new ChoicePrompt("choice"));
_dialogsWaterfall.Add(new ConfirmPrompt("confirm"));
}
....
}
這邊定義了一個DialogSet,裡面第一個是一個Waterfall
dialog,其中定義了好多個steps。
再來增加了一些prompt,在這些steps可以呼叫使用。
准備好了之後,接下來是定義每一個steps。
定義每一個step要做的事情
每一個steps都可以取得上一個steps結束的時候使用者輸入的内容。
因此,每一個steps,可以依照上一個steps的結果做出處理,然後觸發往下要執行的prompt或者waterfall結束。
接下來看看每一個step的定義:
private async Task<DialogTurnResult> GetStartStayDateAsync
(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
return await stepContext.PromptAsync("dateTime",
new PromptOptions()
{
Prompt = MessageFactory.Text("請輸入入住日期"),
},
cancellationToken);
}
private async Task<DialogTurnResult> GetStayDayAsync
(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
(await GetCounterState(stepContext.Context))
.RoomReservation.StartDate =
DateTime.Parse(((List<DateTimeResolution>)stepContext.Result).First().Value);
return await stepContext.PromptAsync("number", new PromptOptions()
{
Prompt = MessageFactory.Text("請輸入要住幾天"),
},
cancellationToken);
}
private async Task<DialogTurnResult> GetNumberOfOccupantAsync
(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
(await GetCounterState(stepContext.Context))
.RoomReservation.NumberOfNightToStay = (int)stepContext.Result - 1;
return await stepContext.PromptAsync("number",
new PromptOptions()
{
Prompt = MessageFactory.Text("幾人入住"),
},
cancellationToken);
}
private async Task<DialogTurnResult> GetBedSizeAsync
(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
(await GetCounterState(stepContext.Context))
.RoomReservation.NumberOfPepole = (int)stepContext.Result;
var choices = new List<Choice>()
{
new Choice("單人床"),
new Choice("雙人床"),
};
return await stepContext.PromptAsync("choice",
new PromptOptions()
{
Prompt = MessageFactory.Text("請選擇床型"),
Choices = choices,
},
cancellationToken);
}
private async Task<DialogTurnResult> GetConfirmAsync
(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var roomReservation = (await GetCounterState(stepContext.Context))
.RoomReservation;
roomReservation.BedSize = ((FoundChoice)stepContext.Result).Value;
return await stepContext.PromptAsync("confirm", new PromptOptions()
{
Prompt = MessageFactory.Text($"請確認您的訂房條件:{Environment.NewLine}" +
$"{roomReservation}")
});
}
private async Task<DialogTurnResult> GetSummaryAsync
(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
if ((bool)stepContext.Result)
{
await stepContext.Context.SendActivityAsync
($"訂單下定完成,訂單號:{DateTime.Now.Ticks}");
}
else
{
await stepContext.Context.SendActivityAsync("已經取消訂單");
}
return await stepContext.EndDialogAsync(cancellationToken: cancellationToken);
}
GetCounterState
,目的是爲了取得CounterState
。
到這裡Dialog就准備好了。
修改bot邏輯呼叫DialogSet
在OnTurnAsync
會加入以下方式來啓動waterflow dialog:
...
var dialogWaterfallContext = await _dialogsWaterfall.CreateContextAsync(turnContext, cancellationToken);
var waterfallResult = await dialogWaterfallContext.ContinueDialogAsync(cancellationToken);
if(turnContext.Activity.Text == "訂房")
{
await dialogWaterfallContext.BeginDialogAsync("formFlow",
null, cancellationToken);
}
else if(waterfallResult.Status != DialogTurnStatus.Empty)
{
}
...
測試
最後測試下來就會變成:
結語
這篇介紹了waterfall這個dialog,可以看到透過step的方式可以自己定義每一個階段要做什麽。
不過,這裡也有注意到一件事情,那就是取得姓名的部分被暫時拿掉了,原因是控制誰在執行透過目前方式有點麻煩。
可是沒辦法解決嗎?畢竟好幾個不同的類型模組是很重要。
下一篇([10]在Dialog裡面做Branching以及Looping把不同功能更加模組化)看看解決方式,怎麽把兩個功能(取得姓名以及訂房)整合在一起同時存在。