-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Simple dialogs for hotel's room booking fully implemented
- Loading branch information
1 parent
692e843
commit 54b8812
Showing
14 changed files
with
350 additions
and
130 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
|
||
from .room_reservation_dialog import RoomReservationDialog | ||
from .booking_room_dialog import BookingRoomDialog | ||
from .main_dialog import MainDialog | ||
|
||
__all__ = ["RoomReservationDialog"] | ||
__all__ = ["BookingRoomDialog", "MainDialog"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
|
||
from botbuilder.schema import ChannelAccount, CardAction, ActionTypes, SuggestedActions, Activity, ActivityTypes | ||
from botbuilder.dialogs import ComponentDialog, WaterfallDialog, WaterfallStepContext, DialogTurnResult | ||
from botbuilder.dialogs.prompts import TextPrompt, NumberPrompt, ChoicePrompt, ConfirmPrompt, AttachmentPrompt, PromptOptions, PromptValidatorContext | ||
from botbuilder.dialogs.choices import Choice | ||
from botbuilder.core import MessageFactory, UserState | ||
|
||
from src.nlu import Intent, NLU | ||
from .utils import Emoji | ||
from .helpers import NLUHelper | ||
from .data_models import RoomReservation | ||
|
||
|
||
class BookingRoomDialog(ComponentDialog): | ||
|
||
def __init__(self, nlu_recognizer: NLU, user_state: UserState): | ||
super(BookingRoomDialog, self).__init__(BookingRoomDialog.__name__) | ||
|
||
# Load the NLU module | ||
self._nlu_recognizer = nlu_recognizer | ||
|
||
# Load the RoomReservation class | ||
self.room_reservation_accessor = user_state.create_property("RoomReservation") | ||
|
||
# Setup the waterfall dialog | ||
self.add_dialog(WaterfallDialog("WFBookingDialog", [ | ||
self.people_step, | ||
self.duration_step, | ||
self.breakfast_step, | ||
self.summary_step, | ||
])) | ||
|
||
# Append the prompts and custom prompts | ||
self.add_dialog(NumberPrompt("PeoplePrompt", BookingRoomDialog.people_prompt_validator)) | ||
self.add_dialog(NumberPrompt("DurationPrompt", BookingRoomDialog.duration_prompt_validator)) | ||
self.add_dialog(ConfirmPrompt("IsTakingBreakfastPrompt")) | ||
|
||
self.initial_dialog_id = "WFBookingDialog" | ||
|
||
@staticmethod | ||
async def people_step(step_context: WaterfallStepContext) -> DialogTurnResult: | ||
"""Ask the user: how many people to make the reservation?""" | ||
|
||
# Retrieve the booking keywords | ||
booking_keywords: dict = step_context.options | ||
step_context.values['booking_keywords'] = booking_keywords | ||
|
||
# If the keyword 'people' exists and is filled, pass the question | ||
if 'people' in booking_keywords and booking_keywords['people'] is not None: | ||
return await step_context.next(booking_keywords['people']) | ||
|
||
# Give user suggestions (1 or 2 people). | ||
# The user can still write a custom number of people [1, 4]. | ||
options = PromptOptions( | ||
prompt=Activity( | ||
|
||
type=ActivityTypes.message, | ||
text="Would you like a single or a double room?", | ||
|
||
suggested_actions=SuggestedActions( | ||
actions=[ | ||
CardAction( | ||
title="Single", | ||
type=ActionTypes.im_back, | ||
value="Single room (1 people)" | ||
), | ||
CardAction( | ||
title="Double", | ||
type=ActionTypes.im_back, | ||
value="Double room (2 peoples)" | ||
) | ||
] | ||
) | ||
), | ||
retry_prompt=MessageFactory.text( | ||
"Reservations can be made for one to four people only." | ||
) | ||
) | ||
|
||
# NumberPrompt - How many people ? | ||
return await step_context.prompt( | ||
"PeoplePrompt", | ||
options | ||
) | ||
|
||
@staticmethod | ||
async def duration_step(step_context: WaterfallStepContext) -> DialogTurnResult: | ||
"""Ask the user: how many night to reserve?""" | ||
|
||
# Save the number of people | ||
step_context.values["people"] = step_context.result | ||
|
||
# Retrieve the keywords | ||
booking_keywords: dict = step_context.values["booking_keywords"] | ||
|
||
# If the keyword 'duration' exists and is filled, pass the question | ||
if 'duration' in booking_keywords and booking_keywords['duration'] is not None: | ||
return await step_context.next(booking_keywords['duration']) | ||
|
||
# NumberPrompt - How many nights ? (duration) | ||
return await step_context.prompt( | ||
"DurationPrompt", | ||
PromptOptions( | ||
prompt=MessageFactory.text("How long do you want to stay?"), | ||
retry_prompt=MessageFactory.text( | ||
"It is only possible to book from 1 to 7 nights" | ||
), | ||
), | ||
) | ||
|
||
@staticmethod | ||
async def breakfast_step(step_context: WaterfallStepContext) -> DialogTurnResult: | ||
|
||
# Save the number of nights | ||
step_context.values["duration"] = step_context.result | ||
|
||
# Confirm people and duration | ||
await step_context.context.send_activity( | ||
MessageFactory.text( | ||
f"Okay, so {step_context.values['people']} people for {step_context.values['duration']} nights" | ||
) | ||
) | ||
|
||
# ConfirmPrompt - Is taking breakfast ? | ||
return await step_context.prompt( | ||
"IsTakingBreakfastPrompt", | ||
PromptOptions( | ||
prompt=MessageFactory.text("Will you be having breakfast?") | ||
), | ||
) | ||
|
||
async def summary_step(self, step_context: WaterfallStepContext) -> DialogTurnResult: | ||
|
||
# Save if the user take the breakfast (bool) | ||
step_context.values["breakfast"] = step_context.result | ||
|
||
# If the user said "Yes": | ||
if step_context.result: | ||
|
||
# Confirm breakfast hour | ||
await step_context.context.send_activity( | ||
MessageFactory.text(f"Perfect, breakfast is from 6am to 10am") | ||
) | ||
|
||
# Save information to Reservation object | ||
room_reservation = await self.room_reservation_accessor.get( | ||
step_context.context, RoomReservation | ||
) | ||
|
||
room_reservation.people = step_context.values["people"] | ||
room_reservation.duration = step_context.values["duration"] | ||
room_reservation.breakfast = step_context.values["breakfast"] | ||
|
||
# End the dialog | ||
await step_context.context.send_activity( | ||
MessageFactory.text("Your booking has been made !") | ||
) | ||
|
||
return await step_context.end_dialog() | ||
|
||
@staticmethod | ||
async def people_prompt_validator(prompt_context: PromptValidatorContext) -> bool: | ||
"""Validate the number of people entered by the user.""" | ||
|
||
# Restrict people between [1 and 4]. | ||
return ( | ||
prompt_context.recognized.succeeded | ||
and 1 <= prompt_context.recognized.value <= 4 | ||
) | ||
|
||
@staticmethod | ||
async def duration_prompt_validator(prompt_context: PromptValidatorContext) -> bool: | ||
"""Validate the number of nights entered by the user.""" | ||
|
||
# Restrict nights between [1 and 7]. | ||
return ( | ||
prompt_context.recognized.succeeded | ||
and 1 <= prompt_context.recognized.value <= 7 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
|
||
from .dialogs_helper import DialogHelper | ||
from .nlu_helper import NLUHelper | ||
|
||
__all__ = ["DialogHelper"] | ||
__all__ = ["DialogHelper", "NLUHelper"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
|
||
from src.nlu import Intent, NLU | ||
|
||
|
||
class NLUHelper: | ||
|
||
@staticmethod | ||
async def execute_nlu_query(nlu_recognizer: NLU, message: str) -> (Intent, dict): | ||
|
||
return nlu_recognizer.get_intent(message) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
|
||
from botbuilder.schema import InputHints | ||
from botbuilder.dialogs import ComponentDialog, WaterfallDialog, WaterfallStepContext, DialogTurnResult | ||
from botbuilder.dialogs.prompts import TextPrompt, PromptOptions | ||
from botbuilder.core import MessageFactory, UserState | ||
|
||
from src.nlu import Intent, NLU | ||
from . import BookingRoomDialog | ||
from .utils import Emoji | ||
from .helpers import NLUHelper | ||
|
||
|
||
class MainDialog(ComponentDialog): | ||
|
||
def __init__(self, nlu_recognizer: NLU, user_state: UserState, | ||
booking_room_dialog: BookingRoomDialog): | ||
|
||
super(MainDialog, self).__init__(MainDialog.__name__) | ||
|
||
# Load the NLU module | ||
self._nlu_recognizer = nlu_recognizer | ||
|
||
# Load the sub-dialogs | ||
self._booking_dialog_id = booking_room_dialog.id | ||
|
||
# Setup the waterfall dialog | ||
self.add_dialog(WaterfallDialog(WaterfallDialog.__name__, [ | ||
self.intro_step, | ||
self.act_step, | ||
self.final_step | ||
])) | ||
|
||
# Append the prompts and custom dialogs, used in the waterfall | ||
self.add_dialog(TextPrompt("ActPrompt")) | ||
self.add_dialog(booking_room_dialog) | ||
|
||
self.initial_dialog_id = WaterfallDialog.__name__ | ||
|
||
@staticmethod | ||
async def intro_step(step_context: WaterfallStepContext) -> DialogTurnResult: | ||
""" | ||
Intro step. Triggered upon any interaction from the user to this bot. | ||
""" | ||
|
||
# Ask what to do | ||
message = ( | ||
str(step_context.options) | ||
if step_context.options | ||
else "What can I help you with today?" | ||
) | ||
|
||
# TextPromp - How can I help you ? | ||
return await step_context.prompt( | ||
"ActPrompt", | ||
PromptOptions( | ||
prompt=MessageFactory.text(message) | ||
), | ||
) | ||
|
||
async def act_step(self, step_context: WaterfallStepContext) -> DialogTurnResult: | ||
""" | ||
Act step. Take user response and infer its intention. | ||
Dispatch to the desired sub-dialog | ||
""" | ||
|
||
intent, keywords = await NLUHelper.execute_nlu_query( | ||
self._nlu_recognizer, step_context.result | ||
) | ||
|
||
# Run the BookingRoomDialog, passing it keywords from nlu | ||
if intent == Intent.BOOK_ROOM: | ||
return await step_context.begin_dialog(self._booking_dialog_id, keywords) | ||
|
||
# If no intent was understood, return a didn't understand message | ||
else: | ||
didnt_understand_text = ( | ||
"Sorry, I didn't get that. Please try asking in a different way" | ||
) | ||
|
||
await step_context.context.send_activity( | ||
MessageFactory.text( | ||
didnt_understand_text, didnt_understand_text, InputHints.ignoring_input | ||
) | ||
) | ||
|
||
return await step_context.next(None) | ||
|
||
async def final_step(self, step_context: WaterfallStepContext) -> DialogTurnResult: | ||
""" | ||
Final step. Triggered upon sub-dialog completion. Replace the current | ||
dialog by the main dialog to start a new loop of conversation. | ||
""" | ||
|
||
# Replace the current dialog back to main dialog | ||
return await step_context.replace_dialog( | ||
self.id, | ||
"What else can I do for you?" | ||
) |
Oops, something went wrong.