From b35a7d8183ca25be390141fcebcf0dab19abee29 Mon Sep 17 00:00:00 2001 From: litofish Date: Thu, 8 Aug 2019 10:33:18 -0700 Subject: [PATCH 01/23] Add room service dialog files --- .../hospitalityskill/Dialogs/MainDialog.cs | 7 ++++ .../Dialogs/RoomServiceDialog.cs | 41 +++++++++++++++++++ .../hospitalityskill/HospitalitySkill.csproj | 11 +++++ .../RoomService/RoomServiceResponses.cs | 17 ++++++++ .../RoomService/RoomServiceResponses.json | 11 +++++ .../RoomService/RoomServiceResponses.tt | 3 ++ .../Services/HospitalityLuis.cs | 3 +- .../experimental/hospitalityskill/Startup.cs | 5 ++- 8 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 skills/src/csharp/experimental/hospitalityskill/Dialogs/RoomServiceDialog.cs create mode 100644 skills/src/csharp/experimental/hospitalityskill/Responses/RoomService/RoomServiceResponses.cs create mode 100644 skills/src/csharp/experimental/hospitalityskill/Responses/RoomService/RoomServiceResponses.json create mode 100644 skills/src/csharp/experimental/hospitalityskill/Responses/RoomService/RoomServiceResponses.tt diff --git a/skills/src/csharp/experimental/hospitalityskill/Dialogs/MainDialog.cs b/skills/src/csharp/experimental/hospitalityskill/Dialogs/MainDialog.cs index 3ed532896e..f62d398a69 100644 --- a/skills/src/csharp/experimental/hospitalityskill/Dialogs/MainDialog.cs +++ b/skills/src/csharp/experimental/hospitalityskill/Dialogs/MainDialog.cs @@ -39,6 +39,7 @@ public MainDialog( ExtendStayDialog extendStayDialog, GetReservationDialog getReservationDialog, RequestItemDialog requestItemDialog, + RoomServiceDialog roomServiceDialog, IBotTelemetryClient telemetryClient) : base(nameof(MainDialog), telemetryClient) { @@ -57,6 +58,7 @@ public MainDialog( AddDialog(extendStayDialog ?? throw new ArgumentNullException(nameof(extendStayDialog))); AddDialog(getReservationDialog ?? throw new ArgumentNullException(nameof(getReservationDialog))); AddDialog(requestItemDialog ?? throw new ArgumentNullException(nameof(requestItemDialog))); + AddDialog(roomServiceDialog ?? throw new ArgumentNullException(nameof(roomServiceDialog))); } protected override async Task OnStartAsync(DialogContext dc, CancellationToken cancellationToken = default(CancellationToken)) @@ -126,6 +128,11 @@ public MainDialog( break; } + case HospitalityLuis.Intent.RoomService: + { + break; + } + case HospitalityLuis.Intent.None: { // No intent was identified, send confused message diff --git a/skills/src/csharp/experimental/hospitalityskill/Dialogs/RoomServiceDialog.cs b/skills/src/csharp/experimental/hospitalityskill/Dialogs/RoomServiceDialog.cs new file mode 100644 index 0000000000..6c91d26168 --- /dev/null +++ b/skills/src/csharp/experimental/hospitalityskill/Dialogs/RoomServiceDialog.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using HospitalitySkill.Services; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Builder.Solutions.Responses; + +namespace HospitalitySkill.Dialogs +{ + public class RoomServiceDialog : HospitalityDialogBase + { + private HotelService _hotelService; + + public RoomServiceDialog( + BotSettings settings, + BotServices services, + ResponseManager responseManager, + ConversationState conversationState, + UserState userState, + HotelService hotelService, + IBotTelemetryClient telemetryClient) + : base(nameof(RoomServiceDialog), settings, services, responseManager, conversationState, userState, hotelService, telemetryClient) + { + var roomService = new WaterfallStep[] + { + HasCheckedOut + }; + + _hotelService = hotelService; + + AddDialog(new WaterfallDialog(nameof(RoomServiceDialog), roomService)); + } + + private class DialogIds + { + + } + } +} diff --git a/skills/src/csharp/experimental/hospitalityskill/HospitalitySkill.csproj b/skills/src/csharp/experimental/hospitalityskill/HospitalitySkill.csproj index 0402b0a07a..cf1c3bd74b 100644 --- a/skills/src/csharp/experimental/hospitalityskill/HospitalitySkill.csproj +++ b/skills/src/csharp/experimental/hospitalityskill/HospitalitySkill.csproj @@ -20,6 +20,7 @@ + @@ -42,6 +43,7 @@ + @@ -110,6 +112,10 @@ TextTemplatingFileGenerator CheckOutResponses.cs + + TextTemplatingFileGenerator + RoomServiceResponses.cs + TextTemplatingFileGenerator SharedResponses.cs @@ -206,6 +212,11 @@ True CheckOutResponses.tt + + True + True + RoomServiceResponses.tt + True True diff --git a/skills/src/csharp/experimental/hospitalityskill/Responses/RoomService/RoomServiceResponses.cs b/skills/src/csharp/experimental/hospitalityskill/Responses/RoomService/RoomServiceResponses.cs new file mode 100644 index 0000000000..07c360a2c9 --- /dev/null +++ b/skills/src/csharp/experimental/hospitalityskill/Responses/RoomService/RoomServiceResponses.cs @@ -0,0 +1,17 @@ +// https://docs.microsoft.com/en-us/visualstudio/modeling/t4-include-directive?view=vs-2017 +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Bot.Builder.Solutions.Responses; + +namespace HospitalitySkill.Responses.RoomService +{ + /// + /// Contains bot responses. + /// + public class RoomServiceResponses : IResponseIdCollection + { + // Generated accessors + public const string MenuPrompt = "MenuPrompt"; + } +} \ No newline at end of file diff --git a/skills/src/csharp/experimental/hospitalityskill/Responses/RoomService/RoomServiceResponses.json b/skills/src/csharp/experimental/hospitalityskill/Responses/RoomService/RoomServiceResponses.json new file mode 100644 index 0000000000..5fd1dd2a2a --- /dev/null +++ b/skills/src/csharp/experimental/hospitalityskill/Responses/RoomService/RoomServiceResponses.json @@ -0,0 +1,11 @@ +{ + "MenuPrompt": { + "replies": [ + { + "text": "There are 4 menus: **Breakfast, Lunch, Dinner and 24 Hour Options**. Which menu would you like to see?", + "speak": "There are 4 menus: **Breakfast, Lunch, Dinner and 24 Hour Options**. Which menu would you like to see?" + } + ], + "inputHint": "expectingInput" + } +} \ No newline at end of file diff --git a/skills/src/csharp/experimental/hospitalityskill/Responses/RoomService/RoomServiceResponses.tt b/skills/src/csharp/experimental/hospitalityskill/Responses/RoomService/RoomServiceResponses.tt new file mode 100644 index 0000000000..f204f0981b --- /dev/null +++ b/skills/src/csharp/experimental/hospitalityskill/Responses/RoomService/RoomServiceResponses.tt @@ -0,0 +1,3 @@ +<#@ template debug="false" hostspecific="true" language="C#" #> +<#@ output extension=".cs" #> +<#@ include file="..\Shared\ResponseIdCollection.t4"#> \ No newline at end of file diff --git a/skills/src/csharp/experimental/hospitalityskill/Services/HospitalityLuis.cs b/skills/src/csharp/experimental/hospitalityskill/Services/HospitalityLuis.cs index fc2c42292c..1b4d035fc2 100644 --- a/skills/src/csharp/experimental/hospitalityskill/Services/HospitalityLuis.cs +++ b/skills/src/csharp/experimental/hospitalityskill/Services/HospitalityLuis.cs @@ -20,7 +20,8 @@ public enum Intent { GetReservationDetails, LateCheckOut, None, - RequestItem + RequestItem, + RoomService }; public Dictionary Intents; diff --git a/skills/src/csharp/experimental/hospitalityskill/Startup.cs b/skills/src/csharp/experimental/hospitalityskill/Startup.cs index 3ac68e8cee..9544e5c640 100644 --- a/skills/src/csharp/experimental/hospitalityskill/Startup.cs +++ b/skills/src/csharp/experimental/hospitalityskill/Startup.cs @@ -11,6 +11,7 @@ using HospitalitySkill.Responses.LateCheckOut; using HospitalitySkill.Responses.Main; using HospitalitySkill.Responses.RequestItem; +using HospitalitySkill.Responses.RoomService; using HospitalitySkill.Responses.Shared; using HospitalitySkill.Services; using Microsoft.ApplicationInsights; @@ -106,7 +107,8 @@ public void ConfigureServices(IServiceCollection services) new LateCheckOutResponses(), new ExtendStayResponses(), new GetReservationResponses(), - new RequestItemResponses())); + new RequestItemResponses(), + new RoomServiceResponses())); // Register dialogs services.AddTransient(); @@ -114,6 +116,7 @@ public void ConfigureServices(IServiceCollection services) services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); // Configure adapters From 9481b61187002e06cca311b12516387f45cd0868 Mon Sep 17 00:00:00 2001 From: litofish Date: Thu, 8 Aug 2019 15:13:47 -0700 Subject: [PATCH 02/23] Add room service luis model --- .../Deployment/Resources/LU/en/Hospitality.lu | 80 +++++++++++++++++-- .../Services/HospitalityLuis.cs | 27 ++++++- 2 files changed, 98 insertions(+), 9 deletions(-) diff --git a/skills/src/csharp/experimental/hospitalityskill/Deployment/Resources/LU/en/Hospitality.lu b/skills/src/csharp/experimental/hospitalityskill/Deployment/Resources/LU/en/Hospitality.lu index 4dfa3442e2..c1c23d99d2 100644 --- a/skills/src/csharp/experimental/hospitalityskill/Deployment/Resources/LU/en/Hospitality.lu +++ b/skills/src/csharp/experimental/hospitalityskill/Deployment/Resources/LU/en/Hospitality.lu @@ -1,4 +1,4 @@ -> ! Automatically generated by [LUDown CLI](https://github.com/Microsoft/botbuilder-tools/tree/master/Ludown), Tue Jul 30 2019 14:19:42 GMT-0700 (Pacific Daylight Time) +> ! Automatically generated by [LUDown CLI](https://github.com/Microsoft/botbuilder-tools/tree/master/Ludown), Thu Aug 08 2019 15:06:42 GMT-0700 (Pacific Daylight Time) > ! Source LUIS JSON file: stdin @@ -64,6 +64,7 @@ - stay longer - stay {NumNights=three more {HotelNights=days}} + ## GetReservationDetails - check out details - get check out @@ -99,6 +100,7 @@ - check out later - extend my check out time - extend my checking out time +- extend the time i check out - i want a late check out - i want a later check out - i want to check out later @@ -125,22 +127,21 @@ - send an email - whats up + ## RequestItem - can i get {ItemRequest=1 more {Item=bottle of shampoo}} - can i get {ItemRequest=2 extra {Item=towels}} - can i get {ItemRequest=2 {Item=razors}} - can i get {ItemRequest=2 {Item=razors}} and {ItemRequest=3 {Item=towels}} - can i get {ItemRequest=2 {Item=shampoo}} bottles -- can i get {ItemRequest=5 {Item=tea bags}}, {ItemRequest=2 {Item=toothbrushes}}, and {ItemRequest=2 {Item=hand towels}} +- can i get {ItemRequest=5 {Item=blankets}}, {ItemRequest=2 {Item=toothbrushes}}, and {ItemRequest=2 {Item=hand towels}} - can i get a different {Item=pillow} and {Item=sheets} - can i get a {Item=razor} and a {Item=towel} - can i get different {Item=sheets} - can i get {Item=dish soap}, {Item=laundry detergent}, and {Item=hangers} - can i get extra {Item=towels} -- can i get more {Item=coffee} for my room - can i get more {Item=shampoo} - can i get more {Item=soap} -- can i get some {Item=coffee} and {Item=tea} - can i request an item - do you have a {Item=comb} - do you have a {Item=hair dryer} i can use @@ -152,14 +153,14 @@ - i need {ItemRequest=2 more {Item=down pillows}} - i need {ItemRequest=2 more {Item=pillows}} - i need {ItemRequest=2 {Item=toothbrushes}}, {ItemRequest=1 {Item=razor}}, and {ItemRequest=2 more {Item=blankets}} -- i need {ItemRequest=3 {Item=water bottles}} and {ItemRequest=2 {Item=tissue boxes}} +- i need {ItemRequest=3 {Item=toothbrushes}} and {ItemRequest=2 {Item=tissue boxes}} - i need a different {Item=charger} and a {Item=toothbrush} - i need a different {Item=pillow} - i need a {Item=toothbrush} - i need {Item=foam pillows} for my room - i need more {Item=lotion} - i need more {Item=pillows} and {Item=blankets} -- i need more {Item=water} +- i need more {Item=shampoo} - i need something for my room - i want something brought to my room - i want to request an item @@ -172,12 +173,55 @@ - i need a different {Item} +## RoomService +- bring a {FoodRequest={SpecialRequest=gluten friendly} {Food=chocolate muffin}} to my room +- bring me {FoodRequest=3 orders of {Food=mac and cheese}} +- can i get {FoodRequest=1 {Food=roasted vegetable rice bowl} {SpecialRequest=without jalapenos}} and a {Food=chicken quesadilla} +- can i get {FoodRequest=2 {SpecialRequest=gluten free} {Food=muffins}} and {FoodRequest=1 {Food=egg white omelet} {SpecialRequest=without cheese}} +- can i get {FoodRequest=2 more {Food=water bottles}} +- can i get {FoodRequest=3 {Food=cookies}} and {FoodRequest=2 {Food=brownies}} +- can i get a {FoodRequest={Food=muffin} that is {SpecialRequest=gluten free}} +- can i get more {FoodRequest={Food=french fries} {SpecialRequest=with ketchup}} +- can i get room service +- can i get some {Food=yogurt} +- can i order {FoodRequest=2 {Food=turkey clubs}} +- can i order a {FoodRequest={Food=cobb salad} {SpecialRequest=without cheese}} +- can i see a lunch menu +- can i see a room service menu +- can you bring me {FoodRequest=3 {Food=chocolate chip cookies}} +- do you have room service +- i need {FoodRequest=1 more {Food=brownie}} +- i need {FoodRequest=2 {Food=bagel sandwiches}} +- i need {FoodRequest=2 {Food=eggs}}, {Food=toast}, and a {FoodRequest={Food=breakfast sandwich} {SpecialRequest=with extra bacon}} +- i need {FoodRequest=2 {Food=mac and cheeses} {SpecialRequest=with vegetables}} +- i need a {Food=large coffee} and {Food=diet pepsi} +- i need a {Food=scrambled egg burrito} +- i need a {FoodRequest={Food=turkey sandwich} {SpecialRequest=with extra mayo}} now +- i need more {Food=orange juice} +- i want {FoodRequest=2 things of {Food=sorbet}} and an {Food=english breakfast tea} +- i want a {FoodRequest={SpecialRequest=vegetarian} {Food=cobb salad}} +- i want {Food=french fries} +- i want to order a {FoodRequest={SpecialRequest=vegetarian} {Food=omelet}} +- i want to order an {Food=egg white omelet} +- i want to order from the all day menu +- i want to order room service +- order {FoodRequest=2 {Food=muffins}} and {FoodRequest=3 {Food=butter croissants}} +- show me a dinner menu +- what are the room service options for this hotel +- will you bring me a {FoodRequest={SpecialRequest=gluten free} {Food=caesar salad}} + + > # Entity definitions +$Food:simple + $HotelNights:simple $Item:simple +$SpecialRequest:simple + + > # PREBUILT Entity definitions $PREBUILT:datetimeV2 @@ -188,14 +232,34 @@ $PREBUILT:number > # Phrase list definitions $Items:phraseList interchangeable -- shampoo,conditioner,towel,hand towel,bath towel,pillow,pillows,pillow case,blanket,blankets,tissues,tissue box,lotion,moisturizer,body lotion,sheets,down pillow,feather pillow,bar of soap,dish soap,laundry detergent,soap,toothpaste,deodorant,towels,toothbrush,toilet paper,body wash,sunscreen,water,cushion,lip balm,coffee,perfume,toothbrushes,mouthwash,shaving cream,detergent,hand sanitizer,shower gel,hand soap,hand lotion,hand cream,liquid soap,bar soap,paper towels,baby wipes,purell,hair conditioner,laundry soap,fabric softener,facial tissue,kleenex,trash bags,hair dryer,detergent powder,cleaning supplies,garbage bags,toiletries,cutlery,cleaning products,bedding,duvet,comforter,comforters,bedspread,pillowcase,duvets,pillowcases,bedspreads,linens,bedsheets,bedlinen,bedclothes,bed sheets,bedsheet,bedcovers,washcloths,bathmats,bathmat,bed sheet,handtowel,washclothes,toiletpaper,facecloths,handtowels,soap/shampoo,papertowels,babywipes,loofas,kleenexes,washrag,dishsoap,waterbottles,papertowel,qtips,chap stick,q tips,q tip,ziplocs,qtip,band aids,zip loc,ziploc,cottonballs,swabs,cotton tipped,bandaids,ziplock,bandaid,band aid,bandages,zip lock,guaze,swab,neosporin,wipes,gauze,bandage,antiseptic,trashbag,ointment,tweezers,scissors,thermometer,nail clippers,safety pins,mirror,comb,nail file,brush,sewing kit,q-tips,razor,dental floss,hairspray,shower cap,slippers,hairbrush,hair brush,combs,bobby pins,brushes,razors,hairbrushes,deoderant,shampoo/conditioner,shaver,chapstick,antiperspirant,anti perspirant,deodarant,bodywash,facewash,face wash,face cream,eye cream,cup,mug,fork,knife,spoon,bowl,cups,spoons,teacup,plate,pot,glass,plates,bowls,tea,teapot,pan,mugs,teapots,pots,dish +- shampoo,conditioner,towel,hand towel,bath towel,pillow,pillows,pillow case,blanket,blankets,tissues,tissue box,lotion,moisturizer,body lotion,sheets,down pillow,feather pillow,bar of soap,dish soap,laundry detergent,soap,toothpaste,deodorant,towels,toothbrush,toilet paper,body wash,sunscreen,cushion,lip balm,perfume,toothbrushes,mouthwash,shaving cream,detergent,hand sanitizer,shower gel,hand soap,hand lotion,hand cream,liquid soap,bar soap,paper towels,baby wipes,purell,hair conditioner,laundry soap,fabric softener,facial tissue,kleenex,trash bags,hair dryer,detergent powder,cleaning supplies,garbage bags,toiletries,cutlery,cleaning products,bedding,duvet,comforter,comforters,bedspread,pillowcase,duvets,pillowcases,bedspreads,linens,bedsheets,bedlinen,bedclothes,bed sheets,bedsheet,bedcovers,washcloths,bathmats,bathmat,bed sheet,handtowel,washclothes,toiletpaper,facecloths,handtowels,soap/shampoo,papertowels,babywipes,loofas,kleenexes,washrag,dishsoap,waterbottles,papertowel,qtips,chap stick,q tips,q tip,ziplocs,qtip,band aids,zip loc,ziploc,cottonballs,swabs,cotton tipped,bandaids,ziplock,bandaid,band aid,bandages,zip lock,guaze,swab,neosporin,wipes,gauze,bandage,antiseptic,trashbag,ointment,tweezers,scissors,thermometer,nail clippers,safety pins,mirror,comb,nail file,brush,sewing kit,q-tips,razor,dental floss,hairspray,shower cap,slippers,hairbrush,hair brush,combs,bobby pins,brushes,razors,hairbrushes,deoderant,shampoo/conditioner,shaver,chapstick,antiperspirant,anti perspirant,deodarant,bodywash,facewash,face wash,face cream,eye cream,cup,mug,fork,knife,spoon,bowl,cups,spoons,plate,pot,glass,plates,bowls,pan,mugs,pots,dish +$Requests:phraseList interchangeable +- vegetarian,gluten free,gluten friendly,without cheese on top,no tomatoes,with extra bacon,no meat,with extra ketchup,without meat,with extra ice,without nuts,with no lettuce or tomato,with the dressing on the side,on an english muffin,on gluten free bread,without jalapenos,with no mushrooms,without bacon,with milk,with sugar and milk,no dairy,dairy free > # List entities +$Menu:breakfast= +- morning + +$Menu:lunch= + +$Menu:dinner= +- evening +- supper + +$Menu:24 hour= +- 24-hour +- 24 hour options +- all day +- allday + + + > # RegEx entities > # Composite entities +$FoodRequest:[number, Food, SpecialRequest] $ItemRequest:[number, Item] -$NumNights:[number, HotelNights] \ No newline at end of file +$NumNights:[number, HotelNights] diff --git a/skills/src/csharp/experimental/hospitalityskill/Services/HospitalityLuis.cs b/skills/src/csharp/experimental/hospitalityskill/Services/HospitalityLuis.cs index 1b4d035fc2..137ceba082 100644 --- a/skills/src/csharp/experimental/hospitalityskill/Services/HospitalityLuis.cs +++ b/skills/src/csharp/experimental/hospitalityskill/Services/HospitalityLuis.cs @@ -20,7 +20,7 @@ public enum Intent { GetReservationDetails, LateCheckOut, None, - RequestItem, + RequestItem, RoomService }; public Dictionary Intents; @@ -30,12 +30,33 @@ public class _Entities // Simple entities public string[] HotelNights; public string[] Item; + public string[] SpecialRequest; + public string[] Food; // Built-in entities public DateTimeSpec[] datetime; public double[] number; + // Lists + public string[][] Menu; + // Composites + public class _InstanceFoodRequest + { + public InstanceData[] number; + public InstanceData[] Food; + public InstanceData[] SpecialRequest; + } + public class FoodRequestClass + { + public double[] number; + public string[] Food; + public string[] SpecialRequest; + [JsonProperty("$instance")] + public _InstanceFoodRequest _instance; + } + public FoodRequestClass[] FoodRequest; + public class _InstanceItemRequest { public InstanceData[] number; @@ -69,8 +90,12 @@ public class _Instance { public InstanceData[] HotelNights; public InstanceData[] Item; + public InstanceData[] SpecialRequest; + public InstanceData[] Food; public InstanceData[] datetime; public InstanceData[] number; + public InstanceData[] Menu; + public InstanceData[] FoodRequest; public InstanceData[] ItemRequest; public InstanceData[] NumNights; } From 7c0aeb26ad5f6e600790f14825f0d0890fe11d98 Mon Sep 17 00:00:00 2001 From: litofish Date: Wed, 14 Aug 2019 17:21:41 -0700 Subject: [PATCH 03/23] Complete most of room service dialog --- .../Content/FoodItemCard.json | 52 +++++ .../Content/FoodOrderCard.json | 131 +++++++++++ .../hospitalityskill/Content/MenuCard.json | 61 +++++ .../Content/MenuItemCard.json | 25 ++ .../Data/RoomServiceMenu.json | 126 ++++++++++ .../Deployment/Resources/LU/en/Hospitality.lu | 6 +- .../hospitalityskill/Dialogs/MainDialog.cs | 2 + .../Dialogs/RoomServiceDialog.cs | 220 +++++++++++++++++- .../hospitalityskill/HospitalitySkill.csproj | 10 + .../Models/HospitalitySkillState.cs | 2 + .../Models/RoomService/FoodOrderData.cs | 17 ++ .../Models/RoomService/Menu.cs | 13 ++ .../Models/RoomService/MenuItem.cs | 15 ++ .../Properties/launchSettings.json | 2 +- .../RoomService/RoomServiceResponses.cs | 8 + .../RoomService/RoomServiceResponses.json | 72 ++++++ .../hospitalityskill/Services/HotelService.cs | 47 +++- .../Services/IHotelService.cs | 6 + 18 files changed, 808 insertions(+), 7 deletions(-) create mode 100644 skills/src/csharp/experimental/hospitalityskill/Content/FoodItemCard.json create mode 100644 skills/src/csharp/experimental/hospitalityskill/Content/FoodOrderCard.json create mode 100644 skills/src/csharp/experimental/hospitalityskill/Content/MenuCard.json create mode 100644 skills/src/csharp/experimental/hospitalityskill/Content/MenuItemCard.json create mode 100644 skills/src/csharp/experimental/hospitalityskill/Data/RoomServiceMenu.json create mode 100644 skills/src/csharp/experimental/hospitalityskill/Models/RoomService/FoodOrderData.cs create mode 100644 skills/src/csharp/experimental/hospitalityskill/Models/RoomService/Menu.cs create mode 100644 skills/src/csharp/experimental/hospitalityskill/Models/RoomService/MenuItem.cs diff --git a/skills/src/csharp/experimental/hospitalityskill/Content/FoodItemCard.json b/skills/src/csharp/experimental/hospitalityskill/Content/FoodItemCard.json new file mode 100644 index 0000000000..78478056d7 --- /dev/null +++ b/skills/src/csharp/experimental/hospitalityskill/Content/FoodItemCard.json @@ -0,0 +1,52 @@ +{ + "type": "AdaptiveCard", + "id": "FoodItemCard", + "body": [ + { + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "items": [ + { + "type": "TextBlock", + "text": "{Quantity}", + "horizontalAlignment": "Center" + } + ], + "width": "40px" + }, + { + "type": "Column", + "items": [ + { + "type": "TextBlock", + "text": "{Name}" + }, + { + "type": "TextBlock", + "text": "{SpecialRequest}", + "isSubtle": true, + "spacing": "None" + } + ], + "width": "stretch" + }, + { + "type": "Column", + "items": [ + { + "type": "TextBlock", + "text": "${Price}", + "horizontalAlignment": "Right" + } + ], + "width": "auto" + } + ] + } + ], + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "version": "1.0", + "speak": "{Speak}" +} \ No newline at end of file diff --git a/skills/src/csharp/experimental/hospitalityskill/Content/FoodOrderCard.json b/skills/src/csharp/experimental/hospitalityskill/Content/FoodOrderCard.json new file mode 100644 index 0000000000..ba129e5108 --- /dev/null +++ b/skills/src/csharp/experimental/hospitalityskill/Content/FoodOrderCard.json @@ -0,0 +1,131 @@ +{ + "type": "AdaptiveCard", + "version": "1.0", + "body": [ + { + "type": "Container", + "backgroundImage": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAmwAAADVCAIAAABlrTvmAAAACXBIWXMAABYlAAAWJQFJUiTwAAAG0mlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDUgNzkuMTYzNDk5LCAyMDE4LzA4LzEzLTE2OjQwOjIyICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoTWFjaW50b3NoKSIgeG1wOkNyZWF0ZURhdGU9IjIwMTktMDMtMjJUMTc6MDM6NDAtMDc6MDAiIHhtcDpNb2RpZnlEYXRlPSIyMDE5LTAzLTIyVDE3OjE2OjU2LTA3OjAwIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDE5LTAzLTIyVDE3OjE2OjU2LTA3OjAwIiBkYzpmb3JtYXQ9ImltYWdlL3BuZyIgcGhvdG9zaG9wOkNvbG9yTW9kZT0iMyIgcGhvdG9zaG9wOklDQ1Byb2ZpbGU9InNSR0IgSUVDNjE5NjYtMi4xIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjY0ZGY4YjdiLTM2MTYtNDRkNy04MTI3LTgyNzk4NmUyODk3ZSIgeG1wTU06RG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOmExNDU0NmE3LWZhODMtMjc0Mi1hNWU0LWIxMzYzNjQ2NWU5NyIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOjhlMDlmYmE5LTE2ZjktNGZiNC05MzdhLThkOTYzMGMxOTQyNiI+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249ImNyZWF0ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6OGUwOWZiYTktMTZmOS00ZmI0LTkzN2EtOGQ5NjMwYzE5NDI2IiBzdEV2dDp3aGVuPSIyMDE5LTAzLTIyVDE3OjAzOjQwLTA3OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoTWFjaW50b3NoKSIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6Yzc5ODg2ZTQtZTg3Zi00MmMzLWI5ZjYtN2FkMDg3YTlkOTg4IiBzdEV2dDp3aGVuPSIyMDE5LTAzLTIyVDE3OjE2OjU2LTA3OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoTWFjaW50b3NoKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6NjRkZjhiN2ItMzYxNi00NGQ3LTgxMjctODI3OTg2ZTI4OTdlIiBzdEV2dDp3aGVuPSIyMDE5LTAzLTIyVDE3OjE2OjU2LTA3OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoTWFjaW50b3NoKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7bJaRFAAAIbUlEQVR4nO3dy27bSAKGUZKWFCfuLKYH6N712/XzzTvNE8ykJzfHkmZRhpN2ZItkFVkXnrOUNj9AgB9IkVD/57/+3QHQuk9fvv33w6fcK6Z5ux/2N/3Fry5/uroh9wAAFtdYQcshogCNU9DliChAyxR0USIK0CwFXZqIArRJQVcgogANUtB1iChAaxR0NSIK0BQFXZOIArRDQVcmogCNUND1iShACxQ0CxEFqJ6C5iKiAHVT0IxEFKBiCpqXiALUSkGzE1GAKtVY0Nu2CtqJKECNKi3ooa2CdiIKUB0FLYeIAtREQYsiogDVUNDSiChAHRS0QCIKUAEFLZOIApROQYslogBFU9CSiShAuRS0cCIKUCgFLZ+IApTo/ttDjQXdDxsqaCeiAAU6nc//+fA594ppNljQTkQBCvTx0/3xeMq9YoJtFrQTUYACff5yn3vCBJstaCeiAKX59nCs6DJ0ywXtRBSgNF++PuSeMNbGC9qJKEBparkMVdBORAFKczxVEFEFDUQUgGkU9ImIAjCBgv5IRAHKctjvck94kYI+I6IAZdnvCj0zK+jPCj1UAJt1OJR4JaqgF4koQFmGvn97e8i94m8U9CUiClCc93dvck/4TkFfIaIAxbm5GX65u829ousU9BoRBSjR+7s3u91N3g0KepWIAhTqn/+4y9hRBR1DRAEKNfR9ro4q6EgiClCuLB1V0PFEFKBoK3dUQScRUYDSrdbR2/2wU9ApRBSgAit0VEFnEFGAOizaUQWdR0QBqrFQRxV0NhEFqEnyjnqSKIaIAlQmYUcVNJKIAtQnSUcVNJ6IAlQpsqMKmoSIAtRqdkcVNBURBajYjI4qaEIiClC3SR1V0LREFKB6IzvqfdDkRBSgBVc7qqBLEFGARrzSUQVdiIgCtONiRxV0OSIK0JRnHVXQRYkoQGueOqqgSxNRgAYNff/H7+/fHXa5hzRORAEadHcYbnfDb7/eHZb8H29EFKA1d4fhcNN3XTcMvY4uSkQBmvJU0EBHFyWiAO14VtBAR5cjogCNuFjQQEcXIqIALXi3f7GggY4uQUQBqvduP7zZXX8fVEeTE1GAuo0saKCjaYkoQMUmFTTQ0YREFKBWMwoa6GgqIgpQpdkFDXQ0CREFqE9kQQMdjSeiAJVJUtBARyOJKEBNrr4POpWOxhBRgGokL2igo7OJKEAdFipooKPziChABRYtaKCjM4goQOlWKGigo1OJKEDRVitooKOTiChAuVYuaKCj44koQKGyFDTQ0ZFEFKBEGQsa6OgYIgpQnOwFDXT0KhEFKEshBQ109HUiClCQogoa6OgrRBSgFAUWNNDRl4goQBGKLWigoxeJKEB+hRc00NGfiShAZlUUNNDRZ0QUIKeKChro6I9EFCCb6goa6OgTEQXIo9KCBjoaiChABlUXNNDRTkQB1tdAQQMdFVGAVTVT0GDjHRVRgPU0VtBgyx0VUYCVNFnQYLMdFVGANTRc0GCbHRVRgMU1X9Bggx0VUYBlbaSgwdY6KqIAC9pUQYNNdVREAZaywYIG2+moiAIsYrMFDYah/+3XX5rvqIgCpLfxggZb6KiIAiR2u+sVNGi+oyIKkNJu6G93Tq3ftd1RRxogpbd759XnGu6ogw2QzOGmdx/3olY7KqIAyewl9GVNdlREAdLo+24/iOhr2uuoiAKkcdMr6HWNdVREAdJwK3ekljoqogBpuBAdr5mOiigAGbTRUREFSON4zr2gNg10VEQB0jiJ6HS1d1REAdI4ns6n3BtqVHVHRRQgmQe3dGept6MiCpDMVxGdq9KOiihAMsfT+V5H56qxoyIKkNLnh5NfRmerrqMiCpDS+dx9vNfR+erqqIgCJHY8nXU0RkUdFVGA9HQ0Ui0dFVGARehopCo6KqIAS3nsqMd15yq/oyIKsKDj6fzxm47OV3hHRRRgWToaqeSOiijA4nQ0UrEdFVGANYSOyuhsZXZURAFWcjyd/3evo/MV2FERBViPjkYqraMiCrAqHY0UOrovo6MiCrA2HY00DP3vZXRURAEy0NFIhXRURAHy0NFIJXRURAGy0dFI2TsqogA56WikvB0VUYDMdDRSxo6KKEB+OhopV0dFFKAIOhopS0dFFKAUOhpp/Y6KKEBBdDTSyh0VUYCy6GikNTsqogDF0dFIq3VURAFKpKOR1umoiAIUKnQ094qKrdBREQUo1/F0/ktHIyzdUREFKJqORlq0oyIKUDodjfTY0X36joooQAV0NNJCHRVRgDroaKQlOiqiANXQ0UjJOyqiADUJHT17gXSutB0VUYDK6GikhB0VUYD66GikVB0VUYAq6Wik0NFDXEdFFKBWOhopvqMiClAxHY0U2VERBaibjkaK6aiIAlRPRyPNfs5IRAFaoKOR5nVURAEaoaORZnRURAHaoaORpv4+KqIATTmezn991dH5JnVURAFaczzraJTxHRVRgAbpaKSRHRVRgDbpaKQxHRVRgGbpaKSrHRVRgJbpaKTXOyqiAI3T0UivdFREAdqno5Fe6qiIAmyCjka62FERBdiK0NGTjs71c0dFFGBDdDTSs46KKMC26GikHzsqogCbo6ORnjoqogBbpKORQkdFFGCjdDTSMPQiCrBdOhpJRAE2zfujMUQUYOt0dDYRBUBHZxJRALpOR2cRUQAePXY094yKiCgA3+noJCIKwN88nHR0LBEF4DkdHUlEAbhAR8cQUQAu09GrRBSAF+no60QUgNfo6CtEFIArdPQlIgrAdTp6kYgCMIqO/kxEARhLR58RUQAm0NEfiSgA0+joExEFYDIdDUQUgDl0tBNRAGbTUREFYL6Nd1REAYiy5Y6KKACxNttREQUggW12VEQBSOOxo1sKqYgCkMzD6fxhSx39P2CJeSCLm9hfAAAAAElFTkSuQmCC", + "items": [ + { + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "items": [ + { + "type": "Image", + "id": "icon", + "horizontalAlignment": "Center", + "url": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAxNiAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTcuMjUgMEM3LjQ1MzEyIDAgNy42Mjg5MSAwLjA3NDIxODggNy43NzczNCAwLjIyMjY1NkM3LjkyNTc4IDAuMzcxMDk0IDggMC41NDY4NzUgOCAwLjc1VjlDOCA5LjQxNDA2IDcuOTE3OTcgOS44MDQ2OSA3Ljc1MzkxIDEwLjE3MTlDNy41OTc2NiAxMC41MzEyIDcuMzgyODEgMTAuODQ3NyA3LjEwOTM4IDExLjEyMTFDNi44NDM3NSAxMS4zODY3IDYuNTI3MzQgMTEuNjAxNiA2LjE2MDE2IDExLjc2NTZDNS43OTI5NyAxMS45MjE5IDUuNDA2MjUgMTIgNSAxMlYyMy4yNUM1IDIzLjQ1MzEgNC45MjU3OCAyMy42Mjg5IDQuNzc3MzQgMjMuNzc3M0M0LjYyODkxIDIzLjkyNTggNC40NTMxMiAyNCA0LjI1IDI0QzQuMDQ2ODggMjQgMy44NzEwOSAyMy45MjU4IDMuNzIyNjYgMjMuNzc3M0MzLjU3NDIyIDIzLjYyODkgMy41IDIzLjQ1MzEgMy41IDIzLjI1VjEyQzMuMDg1OTQgMTIgMi42OTUzMSAxMS45MjE5IDIuMzI4MTIgMTEuNzY1NkMxLjk2ODc1IDExLjYwMTYgMS42NTIzNCAxMS4zODY3IDEuMzc4OTEgMTEuMTIxMUMxLjExMzI4IDEwLjg0NzcgMC44OTg0MzggMTAuNTMxMiAwLjczNDM3NSAxMC4xNzE5QzAuNTc4MTI1IDkuODA0NjkgMC41IDkuNDE0MDYgMC41IDlWMC43NUMwLjUgMC41NDY4NzUgMC41NzQyMTkgMC4zNzEwOTQgMC43MjI2NTYgMC4yMjI2NTZDMC44NzEwOTQgMC4wNzQyMTg4IDEuMDQ2ODggMCAxLjI1IDBDMS40NTMxMiAwIDEuNjI4OTEgMC4wNzQyMTg4IDEuNzc3MzQgMC4yMjI2NTZDMS45MjU3OCAwLjM3MTA5NCAyIDAuNTQ2ODc1IDIgMC43NVY5QzIgOS4yMTA5NCAyLjAzOTA2IDkuNDA2MjUgMi4xMTcxOSA5LjU4NTk0QzIuMTk1MzEgOS43NjU2MiAyLjMwMDc4IDkuOTI1NzggMi40MzM1OSAxMC4wNjY0QzIuNTc0MjIgMTAuMTk5MiAyLjczNDM4IDEwLjMwNDcgMi45MTQwNiAxMC4zODI4QzMuMDkzNzUgMTAuNDYwOSAzLjI4OTA2IDEwLjUgMy41IDEwLjVWMC43NUMzLjUgMC41NDY4NzUgMy41NzQyMiAwLjM3MTA5NCAzLjcyMjY2IDAuMjIyNjU2QzMuODcxMDkgMC4wNzQyMTg4IDQuMDQ2ODggMCA0LjI1IDBDNC40NTMxMiAwIDQuNjI4OTEgMC4wNzQyMTg4IDQuNzc3MzQgMC4yMjI2NTZDNC45MjU3OCAwLjM3MTA5NCA1IDAuNTQ2ODc1IDUgMC43NVYxMC41QzUuMjEwOTQgMTAuNSA1LjQwNjI1IDEwLjQ2MDkgNS41ODU5NCAxMC4zODI4QzUuNzY1NjIgMTAuMzA0NyA1LjkyMTg4IDEwLjE5OTIgNi4wNTQ2OSAxMC4wNjY0QzYuMTk1MzEgOS45MjU3OCA2LjMwNDY5IDkuNzY1NjIgNi4zODI4MSA5LjU4NTk0QzYuNDYwOTQgOS40MDYyNSA2LjUgOS4yMTA5NCA2LjUgOVYwLjc1QzYuNSAwLjU0Njg3NSA2LjU3NDIyIDAuMzcxMDk0IDYuNzIyNjYgMC4yMjI2NTZDNi44NzEwOSAwLjA3NDIxODggNy4wNDY4OCAwIDcuMjUgMFpNMTUuNSAwVjIzLjI1QzE1LjUgMjMuNDUzMSAxNS40MjU4IDIzLjYyODkgMTUuMjc3MyAyMy43NzczQzE1LjEyODkgMjMuOTI1OCAxNC45NTMxIDI0IDE0Ljc1IDI0QzE0LjU0NjkgMjQgMTQuMzcxMSAyMy45MjU4IDE0LjIyMjcgMjMuNzc3M0MxNC4wNzQyIDIzLjYyODkgMTQgMjMuNDUzMSAxNCAyMy4yNVYxNi41QzEzLjcxMDkgMTYuNSAxMy4zOTg0IDE2LjUwMzkgMTMuMDYyNSAxNi41MTE3QzEyLjcyNjYgMTYuNTE5NSAxMi4zOTA2IDE2LjUxNTYgMTIuMDU0NyAxNi41QzExLjcyNjYgMTYuNDc2NiAxMS40MDYyIDE2LjQyOTcgMTEuMDkzOCAxNi4zNTk0QzEwLjc4OTEgMTYuMjgxMiAxMC41MTU2IDE2LjE2MDIgMTAuMjczNCAxNS45OTYxQzEwLjAzOTEgMTUuODI0MiA5Ljg1MTU2IDE1LjYwMTYgOS43MTA5NCAxNS4zMjgxQzkuNTcwMzEgMTUuMDQ2OSA5LjUgMTQuNjg3NSA5LjUgMTQuMjVWNS4yNUM5LjUgNC41MjM0NCA5LjYzNjcyIDMuODQzNzUgOS45MTAxNiAzLjIxMDk0QzEwLjE5MTQgMi41NzAzMSAxMC41NjY0IDIuMDE1NjIgMTEuMDM1MiAxLjU0Njg4QzExLjUxMTcgMS4wNzAzMSAxMi4wNjY0IDAuNjk1MzEyIDEyLjY5OTIgMC40MjE4NzVDMTMuMzM5OCAwLjE0MDYyNSAxNC4wMjM0IDAgMTQuNzUgMEgxNS41Wk0xNCAxLjU3MDMxQzEzLjU2MjUgMS42NTYyNSAxMy4xNjAyIDEuODE2NDEgMTIuNzkzIDIuMDUwNzhDMTIuNDMzNiAyLjI3NzM0IDEyLjExNzIgMi41NTQ2OSAxMS44NDM4IDIuODgyODFDMTEuNTc4MSAzLjIxMDk0IDExLjM3MTEgMy41NzgxMiAxMS4yMjI3IDMuOTg0MzhDMTEuMDc0MiA0LjM4MjgxIDExIDQuODA0NjkgMTEgNS4yNVYxNC4yNUMxMSAxNC40NTMxIDExLjA3NDIgMTQuNjI4OSAxMS4yMjI3IDE0Ljc3NzNDMTEuMzcxMSAxNC45MjU4IDExLjU0NjkgMTUgMTEuNzUgMTVIMTRWMS41NzAzMVoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=", + "size": "Small", + "width": "30px", + "height": "30px" + } + ], + "width": "auto" + }, + { + "type": "Column", + "verticalContentAlignment": "Center", + "items": [ + { + "type": "TextBlock", + "id": "title", + "size": "Large", + "weight": "Bolder", + "color": "Light", + "text": "Current Order" + } + ], + "width": "stretch" + } + ] + } + ] + }, + { + "type": "Container", + "items": [ + { + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "items": [ + { + "type": "TextBlock", + "weight": "Bolder", + "text": "Qty", + "horizontalAlignment": "Center" + } + ], + "width": "40px" + }, + { + "type": "Column", + "items": [ + { + "type": "TextBlock", + "weight": "Bolder", + "text": "Item" + } + ], + "width": "stretch" + }, + { + "type": "Column", + "items": [ + { + "type": "TextBlock", + "weight": "Bolder", + "text": "Amount", + "horizontalAlignment": "Right" + } + ], + "width": "auto" + } + ] + } + ] + }, + { + "type": "Container", + "id": "items", + "items": [] + }, + { + "type": "Container", + "items": [ + { + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "items": [ + { + "type": "TextBlock", + "text": "Bill Total", + "horizontalAlignment": "Right" + } + ], + "width": "stretch" + }, + { + "type": "Column", + "items": [ + { + "type": "TextBlock", + "weight": "Bolder", + "text": "${BillTotal}", + "horizontalAlignment": "Right" + } + ], + "width": "auto" + } + ] + } + ] + } + ], + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json" +} \ No newline at end of file diff --git a/skills/src/csharp/experimental/hospitalityskill/Content/MenuCard.json b/skills/src/csharp/experimental/hospitalityskill/Content/MenuCard.json new file mode 100644 index 0000000000..9130b7f943 --- /dev/null +++ b/skills/src/csharp/experimental/hospitalityskill/Content/MenuCard.json @@ -0,0 +1,61 @@ +{ + "type": "AdaptiveCard", + "id": "MenuCard", + "body": [ + { + "type": "Container", + "backgroundImage": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAmwAAADVCAIAAABlrTvmAAAACXBIWXMAABYlAAAWJQFJUiTwAAAG0mlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDUgNzkuMTYzNDk5LCAyMDE4LzA4LzEzLTE2OjQwOjIyICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoTWFjaW50b3NoKSIgeG1wOkNyZWF0ZURhdGU9IjIwMTktMDMtMjJUMTc6MDM6NDAtMDc6MDAiIHhtcDpNb2RpZnlEYXRlPSIyMDE5LTAzLTIyVDE3OjE2OjU2LTA3OjAwIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDE5LTAzLTIyVDE3OjE2OjU2LTA3OjAwIiBkYzpmb3JtYXQ9ImltYWdlL3BuZyIgcGhvdG9zaG9wOkNvbG9yTW9kZT0iMyIgcGhvdG9zaG9wOklDQ1Byb2ZpbGU9InNSR0IgSUVDNjE5NjYtMi4xIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjY0ZGY4YjdiLTM2MTYtNDRkNy04MTI3LTgyNzk4NmUyODk3ZSIgeG1wTU06RG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOmExNDU0NmE3LWZhODMtMjc0Mi1hNWU0LWIxMzYzNjQ2NWU5NyIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOjhlMDlmYmE5LTE2ZjktNGZiNC05MzdhLThkOTYzMGMxOTQyNiI+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249ImNyZWF0ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6OGUwOWZiYTktMTZmOS00ZmI0LTkzN2EtOGQ5NjMwYzE5NDI2IiBzdEV2dDp3aGVuPSIyMDE5LTAzLTIyVDE3OjAzOjQwLTA3OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoTWFjaW50b3NoKSIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6Yzc5ODg2ZTQtZTg3Zi00MmMzLWI5ZjYtN2FkMDg3YTlkOTg4IiBzdEV2dDp3aGVuPSIyMDE5LTAzLTIyVDE3OjE2OjU2LTA3OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoTWFjaW50b3NoKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6NjRkZjhiN2ItMzYxNi00NGQ3LTgxMjctODI3OTg2ZTI4OTdlIiBzdEV2dDp3aGVuPSIyMDE5LTAzLTIyVDE3OjE2OjU2LTA3OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoTWFjaW50b3NoKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7bJaRFAAAIbUlEQVR4nO3dy27bSAKGUZKWFCfuLKYH6N712/XzzTvNE8ykJzfHkmZRhpN2ZItkFVkXnrOUNj9AgB9IkVD/57/+3QHQuk9fvv33w6fcK6Z5ux/2N/3Fry5/uroh9wAAFtdYQcshogCNU9DliChAyxR0USIK0CwFXZqIArRJQVcgogANUtB1iChAaxR0NSIK0BQFXZOIArRDQVcmogCNUND1iShACxQ0CxEFqJ6C5iKiAHVT0IxEFKBiCpqXiALUSkGzE1GAKtVY0Nu2CtqJKECNKi3ooa2CdiIKUB0FLYeIAtREQYsiogDVUNDSiChAHRS0QCIKUAEFLZOIApROQYslogBFU9CSiShAuRS0cCIKUCgFLZ+IApTo/ttDjQXdDxsqaCeiAAU6nc//+fA594ppNljQTkQBCvTx0/3xeMq9YoJtFrQTUYACff5yn3vCBJstaCeiAKX59nCs6DJ0ywXtRBSgNF++PuSeMNbGC9qJKEBparkMVdBORAFKczxVEFEFDUQUgGkU9ImIAjCBgv5IRAHKctjvck94kYI+I6IAZdnvCj0zK+jPCj1UAJt1OJR4JaqgF4koQFmGvn97e8i94m8U9CUiClCc93dvck/4TkFfIaIAxbm5GX65u829ousU9BoRBSjR+7s3u91N3g0KepWIAhTqn/+4y9hRBR1DRAEKNfR9ro4q6EgiClCuLB1V0PFEFKBoK3dUQScRUYDSrdbR2/2wU9ApRBSgAit0VEFnEFGAOizaUQWdR0QBqrFQRxV0NhEFqEnyjnqSKIaIAlQmYUcVNJKIAtQnSUcVNJ6IAlQpsqMKmoSIAtRqdkcVNBURBajYjI4qaEIiClC3SR1V0LREFKB6IzvqfdDkRBSgBVc7qqBLEFGARrzSUQVdiIgCtONiRxV0OSIK0JRnHVXQRYkoQGueOqqgSxNRgAYNff/H7+/fHXa5hzRORAEadHcYbnfDb7/eHZb8H29EFKA1d4fhcNN3XTcMvY4uSkQBmvJU0EBHFyWiAO14VtBAR5cjogCNuFjQQEcXIqIALXi3f7GggY4uQUQBqvduP7zZXX8fVEeTE1GAuo0saKCjaYkoQMUmFTTQ0YREFKBWMwoa6GgqIgpQpdkFDXQ0CREFqE9kQQMdjSeiAJVJUtBARyOJKEBNrr4POpWOxhBRgGokL2igo7OJKEAdFipooKPziChABRYtaKCjM4goQOlWKGigo1OJKEDRVitooKOTiChAuVYuaKCj44koQKGyFDTQ0ZFEFKBEGQsa6OgYIgpQnOwFDXT0KhEFKEshBQ109HUiClCQogoa6OgrRBSgFAUWNNDRl4goQBGKLWigoxeJKEB+hRc00NGfiShAZlUUNNDRZ0QUIKeKChro6I9EFCCb6goa6OgTEQXIo9KCBjoaiChABlUXNNDRTkQB1tdAQQMdFVGAVTVT0GDjHRVRgPU0VtBgyx0VUYCVNFnQYLMdFVGANTRc0GCbHRVRgMU1X9Bggx0VUYBlbaSgwdY6KqIAC9pUQYNNdVREAZaywYIG2+moiAIsYrMFDYah/+3XX5rvqIgCpLfxggZb6KiIAiR2u+sVNGi+oyIKkNJu6G93Tq3ftd1RRxogpbd759XnGu6ogw2QzOGmdx/3olY7KqIAyewl9GVNdlREAdLo+24/iOhr2uuoiAKkcdMr6HWNdVREAdJwK3ekljoqogBpuBAdr5mOiigAGbTRUREFSON4zr2gNg10VEQB0jiJ6HS1d1REAdI4ns6n3BtqVHVHRRQgmQe3dGept6MiCpDMVxGdq9KOiihAMsfT+V5H56qxoyIKkNLnh5NfRmerrqMiCpDS+dx9vNfR+erqqIgCJHY8nXU0RkUdFVGA9HQ0Ui0dFVGARehopCo6KqIAS3nsqMd15yq/oyIKsKDj6fzxm47OV3hHRRRgWToaqeSOiijA4nQ0UrEdFVGANYSOyuhsZXZURAFWcjyd/3evo/MV2FERBViPjkYqraMiCrAqHY0UOrovo6MiCrA2HY00DP3vZXRURAEy0NFIhXRURAHy0NFIJXRURAGy0dFI2TsqogA56WikvB0VUYDMdDRSxo6KKEB+OhopV0dFFKAIOhopS0dFFKAUOhpp/Y6KKEBBdDTSyh0VUYCy6GikNTsqogDF0dFIq3VURAFKpKOR1umoiAIUKnQ094qKrdBREQUo1/F0/ktHIyzdUREFKJqORlq0oyIKUDodjfTY0X36joooQAV0NNJCHRVRgDroaKQlOiqiANXQ0UjJOyqiADUJHT17gXSutB0VUYDK6GikhB0VUYD66GikVB0VUYAq6Wik0NFDXEdFFKBWOhopvqMiClAxHY0U2VERBaibjkaK6aiIAlRPRyPNfs5IRAFaoKOR5nVURAEaoaORZnRURAHaoaORpv4+KqIATTmezn991dH5JnVURAFaczzraJTxHRVRgAbpaKSRHRVRgDbpaKQxHRVRgGbpaKSrHRVRgJbpaKTXOyqiAI3T0UivdFREAdqno5Fe6qiIAmyCjka62FERBdiK0NGTjs71c0dFFGBDdDTSs46KKMC26GikHzsqogCbo6ORnjoqogBbpKORQkdFFGCjdDTSMPQiCrBdOhpJRAE2zfujMUQUYOt0dDYRBUBHZxJRALpOR2cRUQAePXY094yKiCgA3+noJCIKwN88nHR0LBEF4DkdHUlEAbhAR8cQUQAu09GrRBSAF+no60QUgNfo6CtEFIArdPQlIgrAdTp6kYgCMIqO/kxEARhLR58RUQAm0NEfiSgA0+joExEFYDIdDUQUgDl0tBNRAGbTUREFYL6Nd1REAYiy5Y6KKACxNttREQUggW12VEQBSOOxo1sKqYgCkMzD6fxhSx39P2CJeSCLm9hfAAAAAElFTkSuQmCC", + "items": [ + { + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "items": [ + { + "type": "Image", + "id": "icon", + "horizontalAlignment": "Center", + "url": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAxNiAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTcuMjUgMEM3LjQ1MzEyIDAgNy42Mjg5MSAwLjA3NDIxODggNy43NzczNCAwLjIyMjY1NkM3LjkyNTc4IDAuMzcxMDk0IDggMC41NDY4NzUgOCAwLjc1VjlDOCA5LjQxNDA2IDcuOTE3OTcgOS44MDQ2OSA3Ljc1MzkxIDEwLjE3MTlDNy41OTc2NiAxMC41MzEyIDcuMzgyODEgMTAuODQ3NyA3LjEwOTM4IDExLjEyMTFDNi44NDM3NSAxMS4zODY3IDYuNTI3MzQgMTEuNjAxNiA2LjE2MDE2IDExLjc2NTZDNS43OTI5NyAxMS45MjE5IDUuNDA2MjUgMTIgNSAxMlYyMy4yNUM1IDIzLjQ1MzEgNC45MjU3OCAyMy42Mjg5IDQuNzc3MzQgMjMuNzc3M0M0LjYyODkxIDIzLjkyNTggNC40NTMxMiAyNCA0LjI1IDI0QzQuMDQ2ODggMjQgMy44NzEwOSAyMy45MjU4IDMuNzIyNjYgMjMuNzc3M0MzLjU3NDIyIDIzLjYyODkgMy41IDIzLjQ1MzEgMy41IDIzLjI1VjEyQzMuMDg1OTQgMTIgMi42OTUzMSAxMS45MjE5IDIuMzI4MTIgMTEuNzY1NkMxLjk2ODc1IDExLjYwMTYgMS42NTIzNCAxMS4zODY3IDEuMzc4OTEgMTEuMTIxMUMxLjExMzI4IDEwLjg0NzcgMC44OTg0MzggMTAuNTMxMiAwLjczNDM3NSAxMC4xNzE5QzAuNTc4MTI1IDkuODA0NjkgMC41IDkuNDE0MDYgMC41IDlWMC43NUMwLjUgMC41NDY4NzUgMC41NzQyMTkgMC4zNzEwOTQgMC43MjI2NTYgMC4yMjI2NTZDMC44NzEwOTQgMC4wNzQyMTg4IDEuMDQ2ODggMCAxLjI1IDBDMS40NTMxMiAwIDEuNjI4OTEgMC4wNzQyMTg4IDEuNzc3MzQgMC4yMjI2NTZDMS45MjU3OCAwLjM3MTA5NCAyIDAuNTQ2ODc1IDIgMC43NVY5QzIgOS4yMTA5NCAyLjAzOTA2IDkuNDA2MjUgMi4xMTcxOSA5LjU4NTk0QzIuMTk1MzEgOS43NjU2MiAyLjMwMDc4IDkuOTI1NzggMi40MzM1OSAxMC4wNjY0QzIuNTc0MjIgMTAuMTk5MiAyLjczNDM4IDEwLjMwNDcgMi45MTQwNiAxMC4zODI4QzMuMDkzNzUgMTAuNDYwOSAzLjI4OTA2IDEwLjUgMy41IDEwLjVWMC43NUMzLjUgMC41NDY4NzUgMy41NzQyMiAwLjM3MTA5NCAzLjcyMjY2IDAuMjIyNjU2QzMuODcxMDkgMC4wNzQyMTg4IDQuMDQ2ODggMCA0LjI1IDBDNC40NTMxMiAwIDQuNjI4OTEgMC4wNzQyMTg4IDQuNzc3MzQgMC4yMjI2NTZDNC45MjU3OCAwLjM3MTA5NCA1IDAuNTQ2ODc1IDUgMC43NVYxMC41QzUuMjEwOTQgMTAuNSA1LjQwNjI1IDEwLjQ2MDkgNS41ODU5NCAxMC4zODI4QzUuNzY1NjIgMTAuMzA0NyA1LjkyMTg4IDEwLjE5OTIgNi4wNTQ2OSAxMC4wNjY0QzYuMTk1MzEgOS45MjU3OCA2LjMwNDY5IDkuNzY1NjIgNi4zODI4MSA5LjU4NTk0QzYuNDYwOTQgOS40MDYyNSA2LjUgOS4yMTA5NCA2LjUgOVYwLjc1QzYuNSAwLjU0Njg3NSA2LjU3NDIyIDAuMzcxMDk0IDYuNzIyNjYgMC4yMjI2NTZDNi44NzEwOSAwLjA3NDIxODggNy4wNDY4OCAwIDcuMjUgMFpNMTUuNSAwVjIzLjI1QzE1LjUgMjMuNDUzMSAxNS40MjU4IDIzLjYyODkgMTUuMjc3MyAyMy43NzczQzE1LjEyODkgMjMuOTI1OCAxNC45NTMxIDI0IDE0Ljc1IDI0QzE0LjU0NjkgMjQgMTQuMzcxMSAyMy45MjU4IDE0LjIyMjcgMjMuNzc3M0MxNC4wNzQyIDIzLjYyODkgMTQgMjMuNDUzMSAxNCAyMy4yNVYxNi41QzEzLjcxMDkgMTYuNSAxMy4zOTg0IDE2LjUwMzkgMTMuMDYyNSAxNi41MTE3QzEyLjcyNjYgMTYuNTE5NSAxMi4zOTA2IDE2LjUxNTYgMTIuMDU0NyAxNi41QzExLjcyNjYgMTYuNDc2NiAxMS40MDYyIDE2LjQyOTcgMTEuMDkzOCAxNi4zNTk0QzEwLjc4OTEgMTYuMjgxMiAxMC41MTU2IDE2LjE2MDIgMTAuMjczNCAxNS45OTYxQzEwLjAzOTEgMTUuODI0MiA5Ljg1MTU2IDE1LjYwMTYgOS43MTA5NCAxNS4zMjgxQzkuNTcwMzEgMTUuMDQ2OSA5LjUgMTQuNjg3NSA5LjUgMTQuMjVWNS4yNUM5LjUgNC41MjM0NCA5LjYzNjcyIDMuODQzNzUgOS45MTAxNiAzLjIxMDk0QzEwLjE5MTQgMi41NzAzMSAxMC41NjY0IDIuMDE1NjIgMTEuMDM1MiAxLjU0Njg4QzExLjUxMTcgMS4wNzAzMSAxMi4wNjY0IDAuNjk1MzEyIDEyLjY5OTIgMC40MjE4NzVDMTMuMzM5OCAwLjE0MDYyNSAxNC4wMjM0IDAgMTQuNzUgMEgxNS41Wk0xNCAxLjU3MDMxQzEzLjU2MjUgMS42NTYyNSAxMy4xNjAyIDEuODE2NDEgMTIuNzkzIDIuMDUwNzhDMTIuNDMzNiAyLjI3NzM0IDEyLjExNzIgMi41NTQ2OSAxMS44NDM4IDIuODgyODFDMTEuNTc4MSAzLjIxMDk0IDExLjM3MTEgMy41NzgxMiAxMS4yMjI3IDMuOTg0MzhDMTEuMDc0MiA0LjM4MjgxIDExIDQuODA0NjkgMTEgNS4yNVYxNC4yNUMxMSAxNC40NTMxIDExLjA3NDIgMTQuNjI4OSAxMS4yMjI3IDE0Ljc3NzNDMTEuMzcxMSAxNC45MjU4IDExLjU0NjkgMTUgMTEuNzUgMTVIMTRWMS41NzAzMVoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=", + "size": "Small", + "width": "30px", + "height": "30px" + } + ], + "width": "auto" + }, + { + "type": "Column", + "verticalContentAlignment": "Center", + "items": [ + { + "type": "TextBlock", + "id": "title", + "size": "Large", + "weight": "Bolder", + "color": "Light", + "text": "{Type} Menu" + } + ], + "width": "stretch" + } + ] + }, + { + "type": "TextBlock", + "text": "{TimeAvailable}", + "weight": "Bolder", + "color": "Light" + } + ] + }, + { + "type": "Container", + "id": "items", + "items": [] + } + ], + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "version": "1.0", + "speak": "{Speak}" +} \ No newline at end of file diff --git a/skills/src/csharp/experimental/hospitalityskill/Content/MenuItemCard.json b/skills/src/csharp/experimental/hospitalityskill/Content/MenuItemCard.json new file mode 100644 index 0000000000..a8fa1b9b19 --- /dev/null +++ b/skills/src/csharp/experimental/hospitalityskill/Content/MenuItemCard.json @@ -0,0 +1,25 @@ +{ + "type": "AdaptiveCard", + "id": "MenuItemCard", + "body": [ + { + "type": "Container", + "items": [ + { + "type": "TextBlock", + "text": "{Name} {Price}", + "weight": "Bolder" + }, + { + "type": "TextBlock", + "text": "{Description}", + "wrap": true, + "horizontalAlignment": "Left", + "spacing": "None" + } + ] + } + ], + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "version": "1.0" +} \ No newline at end of file diff --git a/skills/src/csharp/experimental/hospitalityskill/Data/RoomServiceMenu.json b/skills/src/csharp/experimental/hospitalityskill/Data/RoomServiceMenu.json new file mode 100644 index 0000000000..72736b8b16 --- /dev/null +++ b/skills/src/csharp/experimental/hospitalityskill/Data/RoomServiceMenu.json @@ -0,0 +1,126 @@ +[ + { + "type": "Breakfast", + "timeAvailable": "Served daily from 6:00AM - 11:00AM", + "items": [ + { + "name": "Scrambled Egg Burrito", + "allNames": [ "scrambled egg burrito", "scrambled egg burritos", "egg burrito", "egg burritos", "breakfast burrito", "breakfast burritos" ], + "price": 17, + "description": "Chicken sausage link, pepper jack cheese, roasted potatoes, pico de gallo, and cup of fruit" + }, + { + "name": "Toasted Bagel Sandwich", + "allNames": [ "toasted bagel sandwich", "toasted bagel sandwiches", "bagel sandwich", "bagel sandwiches", "breakfast sandwich", "breakfast sandwiches" ], + "price": 15, + "description": "Pork sausage patty, fried egg, cheddar cheese and cup of fruit" + }, + { + "name": "Greek Yogurt Parfait", + "allNames": [ "greek yogurt parfait", "greek yogurt parfaits", "yogurt parfaits", "yogurt parfait", "breakfast parfait", "breakfast parfaits", "parfait", "parfaits" ], + "price": 9, + "description": "Plain greek yogurt, house-made pecan granola, berries" + }, + { + "name": "All Butter Croissant", + "allNames": [ "all butter croissant", "all butter croissants", "butter croissant", "butter croissants", "croissant", "croissants" ], + "price": 5, + "description": "Plain, almond or chocolate available" + } + ] + }, + { + "type": "Lunch", + "timeAvailable": "Served daily from 11:00AM - 4:00PM", + "items": [ + { + "name": "Cobb Salad", + "allNames": [ "cobb salad", "cobb salads" ], + "price": 18, + "description": "Romaine lettuce, house-made bacon, egg, tomatoes, avocado, cheddar cheese, diced chicken" + }, + { + "name": "Chicken Quesadilla", + "allNames": [ "chicken quesadilla", "chicken quesadillas", "quesadilla", "quesadillas" ], + "price": 14, + "description": "Green chiles, cheese blend in flour tortilla, pico de gallo and sour cream" + }, + { + "name": "Smoking Turkey Sandwich", + "allNames": [ "smoking turkey sandwich", "smoking turkey sandwiches", "turkey sandwich", "turkey sandwiches" ], + "price": 17, + "description": "Sourdough bread, mayo, bacon, lettuce, tomato" + }, + { + "name": "Signature Burger", + "allNames": [ "signature burger", "signature burgers", "burger", "burgers" ], + "price": 18, + "description": "Roll, cheddar cheese, bacon, lettuce, tomato, burger sauce" + } + ] + }, + { + "type": "Dinner", + "timeAvailable": "Served daily from 4:00PM - 11:00PM", + "items": [ + { + "name": "Smoking Turkey Sandwich", + "allNames": [ "smoking turkey sandwich", "smoking turkey sandwiches", "turkey sandwich", "turkey sandwiches" ], + "price": 17, + "description": "Sourdough bread, mayo, bacon, lettuce, tomato" + }, + { + "name": "Signature Burger", + "allNames": [ "signature burger", "signature burgers", "burger", "burgers" ], + "price": 18, + "description": "Roll, cheddar cheese, bacon, lettuce, tomato, burger sauce" + }, + { + "name": "Garlic Brushed Salmon Filet", + "allNames": [ "garlic brushed salmon filet", "garlic brushed salmon filets", "garlic salmon", "garlic salmons", "garlic salmon filet", "garlic salmon filets", "salmon", "salmons", "salmon filet", "salmon filets" ], + "price": 38, + "description": "Garlic smashed potatoes and steamed broccolini" + }, + { + "name": "Smoked Mac and Cheese", + "allNames": [ "smoked mac and cheese", "smoked mac and cheeses", "smoked mac & cheese", "smoked mac & cheeses", "mac and cheese", "mac and cheeses", "mac & cheese", "mac & cheeses" ], + "price": 24, + "description": "Cavatappi pasta, smoked cheese blend, tomato basil cream sauce. Add salmon for $12 or sausage for $6" + } + ] + }, + { + "type": "24 Hour", + "timeAvailable": "Served daily", + "items": [ + { + "name": "French Fries", + "allNames": [ "french fries", "french fry" ], + "price": 6 + }, + { + "name": "Chocolate Chip Cookie", + "allNames": [ "chocolate chip cookies", "chocolate chip cookie", "cookie", "cookies" ], + "price": 4 + }, + { + "name": "Red Pepper Hummus", + "allNames": [ "red pepper hummus", "hummus" ], + "price": 12, + "description": "Carrot, celery, pita chips" + }, + { + "name": "Lemon Berry Burst Cake", + "allNames": [ "lemon berry burst cake", "lemon berry burst cakes", "lemon burst cake", "lemon burst cakes", "lemon berry cakes", "lemon berry cake", "lemon cake", "lemon cakes" ], + "price": 8, + "description": "White cake, lemon mousse, berries" + }, + { + "name": "Ice Cream", + "allNames": [ "ice cream", "ice creams", "vanilla ice cream" ], + "price": 7, + "description": "Two scoops of vanilla ice cream" + } + ] + } +] diff --git a/skills/src/csharp/experimental/hospitalityskill/Deployment/Resources/LU/en/Hospitality.lu b/skills/src/csharp/experimental/hospitalityskill/Deployment/Resources/LU/en/Hospitality.lu index c1c23d99d2..d89b345fbc 100644 --- a/skills/src/csharp/experimental/hospitalityskill/Deployment/Resources/LU/en/Hospitality.lu +++ b/skills/src/csharp/experimental/hospitalityskill/Deployment/Resources/LU/en/Hospitality.lu @@ -1,4 +1,4 @@ -> ! Automatically generated by [LUDown CLI](https://github.com/Microsoft/botbuilder-tools/tree/master/Ludown), Thu Aug 08 2019 15:06:42 GMT-0700 (Pacific Daylight Time) +> ! Automatically generated by [LUDown CLI](https://github.com/Microsoft/botbuilder-tools/tree/master/Ludown), Tue Aug 13 2019 09:50:03 GMT-0700 (Pacific Daylight Time) > ! Source LUIS JSON file: stdin @@ -192,13 +192,13 @@ - do you have room service - i need {FoodRequest=1 more {Food=brownie}} - i need {FoodRequest=2 {Food=bagel sandwiches}} -- i need {FoodRequest=2 {Food=eggs}}, {Food=toast}, and a {FoodRequest={Food=breakfast sandwich} {SpecialRequest=with extra bacon}} +- i need {FoodRequest=2 {Food=eggs}}, {Food=toast}, and an {FoodRequest={Food=egg sandwich} {SpecialRequest=with extra bacon}} - i need {FoodRequest=2 {Food=mac and cheeses} {SpecialRequest=with vegetables}} - i need a {Food=large coffee} and {Food=diet pepsi} - i need a {Food=scrambled egg burrito} - i need a {FoodRequest={Food=turkey sandwich} {SpecialRequest=with extra mayo}} now - i need more {Food=orange juice} -- i want {FoodRequest=2 things of {Food=sorbet}} and an {Food=english breakfast tea} +- i want {FoodRequest=2 things of {Food=sorbet}} and an {Food=earl grey tea} - i want a {FoodRequest={SpecialRequest=vegetarian} {Food=cobb salad}} - i want {Food=french fries} - i want to order a {FoodRequest={SpecialRequest=vegetarian} {Food=omelet}} diff --git a/skills/src/csharp/experimental/hospitalityskill/Dialogs/MainDialog.cs b/skills/src/csharp/experimental/hospitalityskill/Dialogs/MainDialog.cs index f62d398a69..e22e750cbf 100644 --- a/skills/src/csharp/experimental/hospitalityskill/Dialogs/MainDialog.cs +++ b/skills/src/csharp/experimental/hospitalityskill/Dialogs/MainDialog.cs @@ -130,6 +130,8 @@ public MainDialog( case HospitalityLuis.Intent.RoomService: { + // ordering room service + turnResult = await dc.BeginDialogAsync(nameof(RoomServiceDialog)); break; } diff --git a/skills/src/csharp/experimental/hospitalityskill/Dialogs/RoomServiceDialog.cs b/skills/src/csharp/experimental/hospitalityskill/Dialogs/RoomServiceDialog.cs index 6c91d26168..3ace0543ff 100644 --- a/skills/src/csharp/experimental/hospitalityskill/Dialogs/RoomServiceDialog.cs +++ b/skills/src/csharp/experimental/hospitalityskill/Dialogs/RoomServiceDialog.cs @@ -1,11 +1,16 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; +using System.Threading; using System.Threading.Tasks; +using HospitalitySkill.Models; +using HospitalitySkill.Responses.RoomService; using HospitalitySkill.Services; using Microsoft.Bot.Builder; using Microsoft.Bot.Builder.Dialogs; using Microsoft.Bot.Builder.Solutions.Responses; +using static Luis.HospitalityLuis._Entities; namespace HospitalitySkill.Dialogs { @@ -25,17 +30,228 @@ public RoomServiceDialog( { var roomService = new WaterfallStep[] { - HasCheckedOut + HasCheckedOut, + MenuPrompt, + ShowMenuCard, + AddItemsPrompt, + ConfirmOrderPrompt, + EndDialog }; _hotelService = hotelService; AddDialog(new WaterfallDialog(nameof(RoomServiceDialog), roomService)); + AddDialog(new TextPrompt(DialogIds.MenuPrompt, ValidateMenuPrompt)); + AddDialog(new TextPrompt(DialogIds.AddMore, ValidateAddItems)); + AddDialog(new ConfirmPrompt(DialogIds.ConfirmOrder)); + AddDialog(new TextPrompt(DialogIds.FoodOrderPrompt, ValidateFoodOrder)); } - private class DialogIds + private async Task MenuPrompt(WaterfallStepContext sc, CancellationToken cancellationToken) + { + var convState = await StateAccessor.GetAsync(sc.Context, () => new HospitalitySkillState()); + convState.FoodList = new List(); + await GetFoodEntities(sc.Context); + + var menu = convState.LuisResult.Entities.Menu; + + // didn't order, prompt if 1 menu type not identified + if (convState.FoodList.Count == 0 && string.IsNullOrWhiteSpace(menu?[0][0]) && menu?.Length != 1) + { + return await sc.PromptAsync(DialogIds.MenuPrompt, new PromptOptions() + { + Prompt = ResponseManager.GetResponse(RoomServiceResponses.MenuPrompt), + RetryPrompt = ResponseManager.GetResponse(RoomServiceResponses.ChooseOneMenu) + }); + } + + return await sc.NextAsync(); + } + + private async Task ValidateMenuPrompt(PromptValidatorContext promptContext, CancellationToken cancellationToken) + { + var convState = await StateAccessor.GetAsync(promptContext.Context, () => new HospitalitySkillState()); + + // can only choose one menu type + var menu = convState.LuisResult.Entities.Menu; + if (promptContext.Recognized.Succeeded && !string.IsNullOrWhiteSpace(menu?[0][0]) && menu.Length == 1) + { + return await Task.FromResult(true); + } + + return await Task.FromResult(false); + } + + private async Task ShowMenuCard(WaterfallStepContext sc, CancellationToken cancellationToken) + { + var convState = await StateAccessor.GetAsync(sc.Context, () => new HospitalitySkillState()); + + if (convState.FoodList.Count == 0) + { + Menu menu = _hotelService.GetMenu(convState.LuisResult.Entities.Menu[0][0]); + + // get available items for requested menu + List menuItems = new List(); + foreach (var item in menu.Items) + { + menuItems.Add(new Card("MenuItemCard", item)); + } + + // show menu card + await sc.Context.SendActivityAsync(ResponseManager.GetCardResponse(null, new Card("MenuCard", menu), null, "items", menuItems)); + + // prompt for order + return await sc.PromptAsync(DialogIds.FoodOrderPrompt, new PromptOptions() + { + Prompt = ResponseManager.GetResponse(RoomServiceResponses.FoodOrder), + RetryPrompt = ResponseManager.GetResponse(RoomServiceResponses.RetryFoodOrder) + }); + } + + return await sc.NextAsync(); + } + + private async Task ValidateFoodOrder(PromptValidatorContext promptContext, CancellationToken cancellationToken) + { + var convState = await StateAccessor.GetAsync(promptContext.Context, () => new HospitalitySkillState()); + var entities = convState.LuisResult.Entities; + + if (promptContext.Recognized.Succeeded && (entities.FoodRequest != null || !string.IsNullOrWhiteSpace(entities.Food?[0]))) + { + await GetFoodEntities(promptContext.Context); + return await Task.FromResult(true); + } + + return await Task.FromResult(false); + } + + private async Task AddItemsPrompt(WaterfallStepContext sc, CancellationToken cancellationToken) + { + await ShowFoodOrder(sc.Context); + + // ask if they want to add more items + return await sc.PromptAsync(DialogIds.AddMore, new PromptOptions() + { + Prompt = ResponseManager.GetResponse(RoomServiceResponses.AddMore) + }); + } + + private async Task ValidateAddItems(PromptValidatorContext promptContext, CancellationToken cancellationToken) + { + var convState = await StateAccessor.GetAsync(promptContext.Context, () => new HospitalitySkillState()); + var entities = convState.LuisResult.Entities; + + if (promptContext.Recognized.Succeeded && (entities.FoodRequest != null || !string.IsNullOrWhiteSpace(entities.Food?[0]))) + { + // added an item + await GetFoodEntities(promptContext.Context); + await ShowFoodOrder(promptContext.Context); + } + + // only asks once + return await Task.FromResult(true); + } + + private async Task ConfirmOrderPrompt(WaterfallStepContext sc, CancellationToken cancellationToken) + { + return await sc.PromptAsync(DialogIds.ConfirmOrder, new PromptOptions() + { + Prompt = ResponseManager.GetResponse(RoomServiceResponses.ConfirmOrder) + }); + } + + private async Task EndDialog(WaterfallStepContext sc, CancellationToken cancellationToken) + { + var confirm = (bool)sc.Result; + if (confirm) + { + await sc.Context.SendActivityAsync(ResponseManager.GetResponse(RoomServiceResponses.FinalOrderConfirmation)); + } + + return await sc.EndDialogAsync(); + } + + // Create and show list of items that were requested but not on the menu + // Build adaptive card of items added to the order + private async Task ShowFoodOrder(ITurnContext turnContext) + { + var convState = await StateAccessor.GetAsync(turnContext, () => new HospitalitySkillState()); + + List notAvailable = new List(); + var unavailableReply = ResponseManager.GetResponse(RoomServiceResponses.ItemsNotAvailable).Text; + + List foodItems = new List(); + var totalFoodOrder = new FoodOrderData { BillTotal = 0 }; + + foreach (var foodRequest in convState.FoodList.ToList()) + { + // get full name of requested item and check availability + var foodItem = _hotelService.CheckMenuItemAvailability(foodRequest.Food[0]); + + if (foodItem == null) + { + // requested item is not available + unavailableReply += Environment.NewLine + "- " + foodRequest.Food[0]; + + notAvailable.Add(foodRequest); + convState.FoodList.Remove(foodRequest); + continue; + } + + var foodItemData = new FoodOrderData + { + Name = foodItem.Name, + Price = foodItem.Price, + Quantity = foodRequest.number == null ? 1 : (int)foodRequest.number[0], + SpecialRequest = foodRequest.SpecialRequest == null ? null : foodRequest.SpecialRequest[0] + }; + + foodItems.Add(new Card("FoodItemCard", foodItemData)); + + // add up bill + totalFoodOrder.BillTotal += foodItemData.Price * foodItemData.Quantity; + } + + // there were items not available + if (notAvailable.Count > 0) + { + await turnContext.SendActivityAsync(unavailableReply); + } + + if (convState.FoodList.Count > 0) + { + await turnContext.SendActivityAsync(ResponseManager.GetCardResponse(null, new Card("FoodOrderCard", totalFoodOrder), null, "items", foodItems)); + } + } + + private async Task GetFoodEntities(ITurnContext turnContext) { + var convState = await StateAccessor.GetAsync(turnContext, () => new HospitalitySkillState()); + var entities = convState.LuisResult.Entities; + if (entities.FoodRequest != null) + { + // food with quantity or special requests + convState.FoodList.AddRange(entities.FoodRequest); + } + + if (!string.IsNullOrWhiteSpace(entities.Food?[0])) + { + // food without quantity or special request + for (int i = 0; i < entities.Food.Length; i++) + { + var foodRequest = new FoodRequestClass { Food = new string[] { entities.Food[i] } }; + convState.FoodList.Add(foodRequest); + } + } + } + + private class DialogIds + { + public const string MenuPrompt = "menuPrompt"; + public const string AddMore = "addMore"; + public const string ConfirmOrder = "confirmOrder"; + public const string FoodOrderPrompt = "foodOrderPrompt"; } } } diff --git a/skills/src/csharp/experimental/hospitalityskill/HospitalitySkill.csproj b/skills/src/csharp/experimental/hospitalityskill/HospitalitySkill.csproj index cf1c3bd74b..16710822d9 100644 --- a/skills/src/csharp/experimental/hospitalityskill/HospitalitySkill.csproj +++ b/skills/src/csharp/experimental/hospitalityskill/HospitalitySkill.csproj @@ -7,8 +7,13 @@ + + + + + @@ -30,8 +35,13 @@ + + + + + diff --git a/skills/src/csharp/experimental/hospitalityskill/Models/HospitalitySkillState.cs b/skills/src/csharp/experimental/hospitalityskill/Models/HospitalitySkillState.cs index 65d5fc47c4..4bf035952c 100644 --- a/skills/src/csharp/experimental/hospitalityskill/Models/HospitalitySkillState.cs +++ b/skills/src/csharp/experimental/hospitalityskill/Models/HospitalitySkillState.cs @@ -19,6 +19,8 @@ public class HospitalitySkillState public List ItemList { get; set; } + public List FoodList { get; set; } + public void Clear() { } diff --git a/skills/src/csharp/experimental/hospitalityskill/Models/RoomService/FoodOrderData.cs b/skills/src/csharp/experimental/hospitalityskill/Models/RoomService/FoodOrderData.cs new file mode 100644 index 0000000000..09b76cc8b3 --- /dev/null +++ b/skills/src/csharp/experimental/hospitalityskill/Models/RoomService/FoodOrderData.cs @@ -0,0 +1,17 @@ +using Microsoft.Bot.Builder.Solutions.Responses; + +namespace HospitalitySkill.Models +{ + public class FoodOrderData : ICardData + { + public string Name { get; set; } + + public string SpecialRequest { get; set; } + + public int Price { get; set; } + + public int Quantity { get; set; } + + public int BillTotal { get; set; } + } +} diff --git a/skills/src/csharp/experimental/hospitalityskill/Models/RoomService/Menu.cs b/skills/src/csharp/experimental/hospitalityskill/Models/RoomService/Menu.cs new file mode 100644 index 0000000000..69d470ba7e --- /dev/null +++ b/skills/src/csharp/experimental/hospitalityskill/Models/RoomService/Menu.cs @@ -0,0 +1,13 @@ +using Microsoft.Bot.Builder.Solutions.Responses; + +namespace HospitalitySkill.Models +{ + public class Menu : ICardData + { + public string Type { get; set; } + + public string TimeAvailable { get; set; } + + public MenuItem[] Items { get; set; } + } +} diff --git a/skills/src/csharp/experimental/hospitalityskill/Models/RoomService/MenuItem.cs b/skills/src/csharp/experimental/hospitalityskill/Models/RoomService/MenuItem.cs new file mode 100644 index 0000000000..26294e8b9f --- /dev/null +++ b/skills/src/csharp/experimental/hospitalityskill/Models/RoomService/MenuItem.cs @@ -0,0 +1,15 @@ +using Microsoft.Bot.Builder.Solutions.Responses; + +namespace HospitalitySkill.Models +{ + public class MenuItem : ICardData + { + public string Name { get; set; } + + public string[] AllNames { get; set; } + + public int Price { get; set; } + + public string Description { get; set; } + } +} diff --git a/skills/src/csharp/experimental/hospitalityskill/Properties/launchSettings.json b/skills/src/csharp/experimental/hospitalityskill/Properties/launchSettings.json index 8f2873b981..ff0e841fa2 100644 --- a/skills/src/csharp/experimental/hospitalityskill/Properties/launchSettings.json +++ b/skills/src/csharp/experimental/hospitalityskill/Properties/launchSettings.json @@ -3,7 +3,7 @@ "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { - "applicationUrl": "http://localhost:3978/", + "applicationUrl": "http://localhost:3999/", "sslPort": 0 } }, diff --git a/skills/src/csharp/experimental/hospitalityskill/Responses/RoomService/RoomServiceResponses.cs b/skills/src/csharp/experimental/hospitalityskill/Responses/RoomService/RoomServiceResponses.cs index 07c360a2c9..b3c83e6130 100644 --- a/skills/src/csharp/experimental/hospitalityskill/Responses/RoomService/RoomServiceResponses.cs +++ b/skills/src/csharp/experimental/hospitalityskill/Responses/RoomService/RoomServiceResponses.cs @@ -13,5 +13,13 @@ public class RoomServiceResponses : IResponseIdCollection { // Generated accessors public const string MenuPrompt = "MenuPrompt"; + public const string ChooseOneMenu = "ChooseOneMenu"; + public const string FoodOrder = "FoodOrder"; + public const string RetryFoodOrder = "RetryFoodOrder"; + public const string ListOrder = "ListOrder"; + public const string ItemsNotAvailable = "ItemsNotAvailable"; + public const string AddMore = "AddMore"; + public const string ConfirmOrder = "ConfirmOrder"; + public const string FinalOrderConfirmation = "FinalOrderConfirmation"; } } \ No newline at end of file diff --git a/skills/src/csharp/experimental/hospitalityskill/Responses/RoomService/RoomServiceResponses.json b/skills/src/csharp/experimental/hospitalityskill/Responses/RoomService/RoomServiceResponses.json index 5fd1dd2a2a..c3d709546e 100644 --- a/skills/src/csharp/experimental/hospitalityskill/Responses/RoomService/RoomServiceResponses.json +++ b/skills/src/csharp/experimental/hospitalityskill/Responses/RoomService/RoomServiceResponses.json @@ -7,5 +7,77 @@ } ], "inputHint": "expectingInput" + }, + "ChooseOneMenu": { + "replies": [ + { + "text": "Please choose one menu to view: Breakfast, Lunch, Dinner or 24 Hour.", + "speak": "Please choose one menu to view: Breakfast, Lunch, Dinner or 24 Hour." + } + ], + "inputHint": "expectingInput" + }, + "FoodOrder": { + "replies": [ + { + "text": "What would you like to order?", + "speak": "What would you like to order?" + } + ], + "inputHint": "expectingInput" + }, + "RetryFoodOrder": { + "replies": [ + { + "text": "Sorry I didn't get that. What would you like to order?", + "speak": "Sorry I didn't get that. What would you like to order?" + } + ], + "inputHint": "expectingInput" + }, + "ListOrder": { + "replies": [ + { + "text": "Here is your current order:", + "speak": "Here is your current order:" + } + ], + "inputHint": "ignoringInput" + }, + "ItemsNotAvailable": { + "replies": [ + { + "text": "Sorry the following items are not available on our menu:", + "speak": "Sorry the following items are not available on our menu" + } + ], + "inputHint": "ignoringInput" + }, + "AddMore": { + "replies": [ + { + "text": "Would you like to add anything else?", + "speak": "Would you like to add anything else?" + } + ], + "inputHint": "expectingInput" + }, + "ConfirmOrder": { + "replies": [ + { + "text": "Shall I place your order? It will be billed to your room.", + "speak": "Shall I place your order? It will be billed to your room." + } + ], + "inputHint": "expectingInput" + }, + "FinalOrderConfirmation": { + "replies": [ + { + "text": "Ok! Your order will be brought to your room shortly.", + "speak": "Ok! Your order will be brought to your room shortly." + } + ], + "inputHint": "ignoringInput" } } \ No newline at end of file diff --git a/skills/src/csharp/experimental/hospitalityskill/Services/HotelService.cs b/skills/src/csharp/experimental/hospitalityskill/Services/HotelService.cs index 9fdcdaf867..78a39c068e 100644 --- a/skills/src/csharp/experimental/hospitalityskill/Services/HotelService.cs +++ b/skills/src/csharp/experimental/hospitalityskill/Services/HotelService.cs @@ -1,6 +1,10 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Threading.Tasks; using HospitalitySkill.Models; +using Newtonsoft.Json; using static Luis.HospitalityLuis._Entities; namespace HospitalitySkill.Services @@ -9,8 +13,15 @@ namespace HospitalitySkill.Services // Should replace with real apis public class HotelService : IHotelService { + private const string RoomServiceMenuFileName = "RoomServiceMenu.json"; + private readonly string _menuFilePath; + public HotelService() { + _menuFilePath = typeof(HotelService).Assembly + .GetManifestResourceNames() + .Where(x => x.Contains(RoomServiceMenuFileName)) + .First(); } public async Task GetLateCheckOutAsync() @@ -37,5 +48,39 @@ public async Task RequestItems(List items) // send request for this list of items to be brought return await Task.FromResult(true); } + + // returns full name of menu item if found + public MenuItem CheckMenuItemAvailability(string item) + { + using (var r = new StreamReader(typeof(HotelService).Assembly.GetManifestResourceStream(_menuFilePath))) + { + string json = r.ReadToEnd(); + Menu[] menus = JsonConvert.DeserializeObject(json); + + // check all menus + foreach (var menu in menus) + { + foreach (var menuItem in menu.Items) + { + if (Array.Exists(menuItem.AllNames, x => string.Equals(x, item, StringComparison.CurrentCultureIgnoreCase))) + { + return menuItem; + } + } + } + + return null; + } + } + + public Menu GetMenu(string menuType) + { + using (var r = new StreamReader(typeof(HotelService).Assembly.GetManifestResourceStream(_menuFilePath))) + { + string json = r.ReadToEnd(); + Menu[] menus = JsonConvert.DeserializeObject(json); + return Array.Find(menus, x => string.Equals(x.Type, menuType, StringComparison.CurrentCultureIgnoreCase)); + } + } } } diff --git a/skills/src/csharp/experimental/hospitalityskill/Services/IHotelService.cs b/skills/src/csharp/experimental/hospitalityskill/Services/IHotelService.cs index e72b7ccc69..27e9755fe9 100644 --- a/skills/src/csharp/experimental/hospitalityskill/Services/IHotelService.cs +++ b/skills/src/csharp/experimental/hospitalityskill/Services/IHotelService.cs @@ -19,5 +19,11 @@ public interface IHotelService // request items to be brought Task RequestItems(List items); + + // check availability of a room service request + MenuItem CheckMenuItemAvailability(string item); + + // get the requested menu to view + Menu GetMenu(string menuType); } } From a2a10f5fa2c8262c7c4ccdb6468b5278d5e6f849 Mon Sep 17 00:00:00 2001 From: litofish Date: Thu, 15 Aug 2019 13:22:01 -0700 Subject: [PATCH 04/23] update menu card --- .../Content/MenuItemCard.json | 51 +++++++++++++++++-- .../Data/RoomServiceMenu.json | 10 ++++ .../Models/RoomService/MenuItem.cs | 4 ++ 3 files changed, 61 insertions(+), 4 deletions(-) diff --git a/skills/src/csharp/experimental/hospitalityskill/Content/MenuItemCard.json b/skills/src/csharp/experimental/hospitalityskill/Content/MenuItemCard.json index a8fa1b9b19..773d4914aa 100644 --- a/skills/src/csharp/experimental/hospitalityskill/Content/MenuItemCard.json +++ b/skills/src/csharp/experimental/hospitalityskill/Content/MenuItemCard.json @@ -1,14 +1,57 @@ { "type": "AdaptiveCard", - "id": "MenuItemCard", + "id": "MenuItemCard", "body": [ { "type": "Container", "items": [ { - "type": "TextBlock", - "text": "{Name} {Price}", - "weight": "Bolder" + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "width": "auto", + "items": [ + { + "type": "TextBlock", + "text": "{Name} {Price}", + "weight": "Bolder" + } + ] + }, + { + "type": "Column", + "width": "auto", + "items": [ + { + "type": "TextBlock", + "spacing": "None", + "text": "GF", + "color": "Warning", + "weight": "Bolder", + "size": "Small", + "isVisible": "{GlutenFree}" + } + ], + "verticalContentAlignment": "Center" + }, + { + "type": "Column", + "spacing": "Small", + "width": "auto", + "items": [ + { + "type": "TextBlock", + "text": "V", + "color": "Good", + "weight": "Bolder", + "size": "Small", + "isVisible": "{Vegetarian}" + } + ], + "verticalContentAlignment": "Center" + } + ] }, { "type": "TextBlock", diff --git a/skills/src/csharp/experimental/hospitalityskill/Data/RoomServiceMenu.json b/skills/src/csharp/experimental/hospitalityskill/Data/RoomServiceMenu.json index 72736b8b16..d233871fc5 100644 --- a/skills/src/csharp/experimental/hospitalityskill/Data/RoomServiceMenu.json +++ b/skills/src/csharp/experimental/hospitalityskill/Data/RoomServiceMenu.json @@ -18,6 +18,7 @@ { "name": "Greek Yogurt Parfait", "allNames": [ "greek yogurt parfait", "greek yogurt parfaits", "yogurt parfaits", "yogurt parfait", "breakfast parfait", "breakfast parfaits", "parfait", "parfaits" ], + "vegetarian": true, "price": 9, "description": "Plain greek yogurt, house-made pecan granola, berries" }, @@ -36,6 +37,8 @@ { "name": "Cobb Salad", "allNames": [ "cobb salad", "cobb salads" ], + "glutenFree": true, + "vegetarian": true, "price": 18, "description": "Romaine lettuce, house-made bacon, egg, tomatoes, avocado, cheddar cheese, diced chicken" }, @@ -78,6 +81,7 @@ { "name": "Garlic Brushed Salmon Filet", "allNames": [ "garlic brushed salmon filet", "garlic brushed salmon filets", "garlic salmon", "garlic salmons", "garlic salmon filet", "garlic salmon filets", "salmon", "salmons", "salmon filet", "salmon filets" ], + "glutenFree": true, "price": 38, "description": "Garlic smashed potatoes and steamed broccolini" }, @@ -96,6 +100,8 @@ { "name": "French Fries", "allNames": [ "french fries", "french fry" ], + "glutenFree": true, + "vegetarian": true, "price": 6 }, { @@ -106,18 +112,22 @@ { "name": "Red Pepper Hummus", "allNames": [ "red pepper hummus", "hummus" ], + "glutenFree": true, + "vegetarian": true, "price": 12, "description": "Carrot, celery, pita chips" }, { "name": "Lemon Berry Burst Cake", "allNames": [ "lemon berry burst cake", "lemon berry burst cakes", "lemon burst cake", "lemon burst cakes", "lemon berry cakes", "lemon berry cake", "lemon cake", "lemon cakes" ], + "vegetarian": true, "price": 8, "description": "White cake, lemon mousse, berries" }, { "name": "Ice Cream", "allNames": [ "ice cream", "ice creams", "vanilla ice cream" ], + "vegetarian": true, "price": 7, "description": "Two scoops of vanilla ice cream" } diff --git a/skills/src/csharp/experimental/hospitalityskill/Models/RoomService/MenuItem.cs b/skills/src/csharp/experimental/hospitalityskill/Models/RoomService/MenuItem.cs index 26294e8b9f..5408b5edb4 100644 --- a/skills/src/csharp/experimental/hospitalityskill/Models/RoomService/MenuItem.cs +++ b/skills/src/csharp/experimental/hospitalityskill/Models/RoomService/MenuItem.cs @@ -8,6 +8,10 @@ public class MenuItem : ICardData public string[] AllNames { get; set; } + public bool GlutenFree { get; set; } + + public bool Vegetarian { get; set; } + public int Price { get; set; } public string Description { get; set; } From 15892dc30021d571351b1f8fd6b8615f04a6272e Mon Sep 17 00:00:00 2001 From: litofish Date: Thu, 15 Aug 2019 13:22:23 -0700 Subject: [PATCH 05/23] improve luis model --- .../Deployment/Resources/LU/en/Hospitality.lu | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/skills/src/csharp/experimental/hospitalityskill/Deployment/Resources/LU/en/Hospitality.lu b/skills/src/csharp/experimental/hospitalityskill/Deployment/Resources/LU/en/Hospitality.lu index d89b345fbc..2cc5a02010 100644 --- a/skills/src/csharp/experimental/hospitalityskill/Deployment/Resources/LU/en/Hospitality.lu +++ b/skills/src/csharp/experimental/hospitalityskill/Deployment/Resources/LU/en/Hospitality.lu @@ -1,4 +1,4 @@ -> ! Automatically generated by [LUDown CLI](https://github.com/Microsoft/botbuilder-tools/tree/master/Ludown), Tue Aug 13 2019 09:50:03 GMT-0700 (Pacific Daylight Time) +> ! Automatically generated by [LUDown CLI](https://github.com/Microsoft/botbuilder-tools/tree/master/Ludown), Thu Aug 15 2019 13:20:17 GMT-0700 (Pacific Daylight Time) > ! Source LUIS JSON file: stdin @@ -180,6 +180,7 @@ - can i get {FoodRequest=2 {SpecialRequest=gluten free} {Food=muffins}} and {FoodRequest=1 {Food=egg white omelet} {SpecialRequest=without cheese}} - can i get {FoodRequest=2 more {Food=water bottles}} - can i get {FoodRequest=3 {Food=cookies}} and {FoodRequest=2 {Food=brownies}} +- can i get a {Food=croissant}, a {Food=quesadilla} and a {FoodRequest={Food=burger} {SpecialRequest=without lettuce}} - can i get a {FoodRequest={Food=muffin} that is {SpecialRequest=gluten free}} - can i get more {FoodRequest={Food=french fries} {SpecialRequest=with ketchup}} - can i get room service @@ -198,8 +199,10 @@ - i need a {Food=scrambled egg burrito} - i need a {FoodRequest={Food=turkey sandwich} {SpecialRequest=with extra mayo}} now - i need more {Food=orange juice} +- i want {FoodRequest=2 {Food=cookies}}, a {FoodRequest={Food=cobb salad} {SpecialRequest=without chicken}} and {FoodRequest=3 {Food=salmons}} - i want {FoodRequest=2 things of {Food=sorbet}} and an {Food=earl grey tea} - i want a {FoodRequest={SpecialRequest=vegetarian} {Food=cobb salad}} +- i want an {Food=omelet}, a {Food=turkey sandwich}, {FoodRequest=2 {Food=cobb salads}} - i want {Food=french fries} - i want to order a {FoodRequest={SpecialRequest=vegetarian} {Food=omelet}} - i want to order an {Food=egg white omelet} @@ -234,7 +237,9 @@ $PREBUILT:number $Items:phraseList interchangeable - shampoo,conditioner,towel,hand towel,bath towel,pillow,pillows,pillow case,blanket,blankets,tissues,tissue box,lotion,moisturizer,body lotion,sheets,down pillow,feather pillow,bar of soap,dish soap,laundry detergent,soap,toothpaste,deodorant,towels,toothbrush,toilet paper,body wash,sunscreen,cushion,lip balm,perfume,toothbrushes,mouthwash,shaving cream,detergent,hand sanitizer,shower gel,hand soap,hand lotion,hand cream,liquid soap,bar soap,paper towels,baby wipes,purell,hair conditioner,laundry soap,fabric softener,facial tissue,kleenex,trash bags,hair dryer,detergent powder,cleaning supplies,garbage bags,toiletries,cutlery,cleaning products,bedding,duvet,comforter,comforters,bedspread,pillowcase,duvets,pillowcases,bedspreads,linens,bedsheets,bedlinen,bedclothes,bed sheets,bedsheet,bedcovers,washcloths,bathmats,bathmat,bed sheet,handtowel,washclothes,toiletpaper,facecloths,handtowels,soap/shampoo,papertowels,babywipes,loofas,kleenexes,washrag,dishsoap,waterbottles,papertowel,qtips,chap stick,q tips,q tip,ziplocs,qtip,band aids,zip loc,ziploc,cottonballs,swabs,cotton tipped,bandaids,ziplock,bandaid,band aid,bandages,zip lock,guaze,swab,neosporin,wipes,gauze,bandage,antiseptic,trashbag,ointment,tweezers,scissors,thermometer,nail clippers,safety pins,mirror,comb,nail file,brush,sewing kit,q-tips,razor,dental floss,hairspray,shower cap,slippers,hairbrush,hair brush,combs,bobby pins,brushes,razors,hairbrushes,deoderant,shampoo/conditioner,shaver,chapstick,antiperspirant,anti perspirant,deodarant,bodywash,facewash,face wash,face cream,eye cream,cup,mug,fork,knife,spoon,bowl,cups,spoons,plate,pot,glass,plates,bowls,pan,mugs,pots,dish $Requests:phraseList interchangeable -- vegetarian,gluten free,gluten friendly,without cheese on top,no tomatoes,with extra bacon,no meat,with extra ketchup,without meat,with extra ice,without nuts,with no lettuce or tomato,with the dressing on the side,on an english muffin,on gluten free bread,without jalapenos,with no mushrooms,without bacon,with milk,with sugar and milk,no dairy,dairy free +- vegetarian,gluten free,gluten friendly,without cheese on top,no tomatoes,with extra bacon,no meat,with extra ketchup,without meat,with extra ice,without nuts,with no lettuce or tomato,with the dressing on the side,on an english muffin,on gluten free bread,without jalapenos,with no mushrooms,without bacon,with milk,with sugar and milk,no dairy,dairy free,with extra cheese,without lettuce,without tomato,without chicken,with extra fruit,without fruit +$Foods:phraseList interchangeable +- scrambled egg burrito,toasted bagel sandwich,butter croissant,yogurt parfait,cobb salad,chicken quesadilla,french fries,red pepper hummus,chocolate chip cookies,turkey sandwich,signature burger,garlic brushed salmon filet,smoked mac and cheese,lemon berry burst cake,ice cream,parfait,cookie,croissant,quesadilla,hummus,salmon > # List entities From 903e57532f65260f60437051e058a4e3ab61d682 Mon Sep 17 00:00:00 2001 From: litofish Date: Thu, 15 Aug 2019 13:49:27 -0700 Subject: [PATCH 06/23] final changes --- .../hospitalityskill/Content/FoodOrderCard.json | 3 ++- .../Deployment/Resources/LU/en/Hospitality.lu | 4 +++- .../hospitalityskill/Dialogs/RoomServiceDialog.cs | 1 - .../Responses/RoomService/RoomServiceResponses.cs | 1 - .../Responses/RoomService/RoomServiceResponses.json | 9 --------- .../hospitalityskill/Services/HotelService.cs | 1 + 6 files changed, 6 insertions(+), 13 deletions(-) diff --git a/skills/src/csharp/experimental/hospitalityskill/Content/FoodOrderCard.json b/skills/src/csharp/experimental/hospitalityskill/Content/FoodOrderCard.json index ba129e5108..1ea3b7f4ab 100644 --- a/skills/src/csharp/experimental/hospitalityskill/Content/FoodOrderCard.json +++ b/skills/src/csharp/experimental/hospitalityskill/Content/FoodOrderCard.json @@ -1,6 +1,7 @@ { "type": "AdaptiveCard", - "version": "1.0", + "version": "1.0", + "id": "FoodOrderCard", "body": [ { "type": "Container", diff --git a/skills/src/csharp/experimental/hospitalityskill/Deployment/Resources/LU/en/Hospitality.lu b/skills/src/csharp/experimental/hospitalityskill/Deployment/Resources/LU/en/Hospitality.lu index 2cc5a02010..782458e479 100644 --- a/skills/src/csharp/experimental/hospitalityskill/Deployment/Resources/LU/en/Hospitality.lu +++ b/skills/src/csharp/experimental/hospitalityskill/Deployment/Resources/LU/en/Hospitality.lu @@ -1,4 +1,4 @@ -> ! Automatically generated by [LUDown CLI](https://github.com/Microsoft/botbuilder-tools/tree/master/Ludown), Thu Aug 15 2019 13:20:17 GMT-0700 (Pacific Daylight Time) +> ! Automatically generated by [LUDown CLI](https://github.com/Microsoft/botbuilder-tools/tree/master/Ludown), Thu Aug 15 2019 13:29:21 GMT-0700 (Pacific Daylight Time) > ! Source LUIS JSON file: stdin @@ -176,6 +176,7 @@ ## RoomService - bring a {FoodRequest={SpecialRequest=gluten friendly} {Food=chocolate muffin}} to my room - bring me {FoodRequest=3 orders of {Food=mac and cheese}} +- can i also add a {Food=cookie} and an {Food=omelet} - can i get {FoodRequest=1 {Food=roasted vegetable rice bowl} {SpecialRequest=without jalapenos}} and a {Food=chicken quesadilla} - can i get {FoodRequest=2 {SpecialRequest=gluten free} {Food=muffins}} and {FoodRequest=1 {Food=egg white omelet} {SpecialRequest=without cheese}} - can i get {FoodRequest=2 more {Food=water bottles}} @@ -191,6 +192,7 @@ - can i see a room service menu - can you bring me {FoodRequest=3 {Food=chocolate chip cookies}} - do you have room service +- i also want {FoodRequest=2 {Food=parfaits}} - i need {FoodRequest=1 more {Food=brownie}} - i need {FoodRequest=2 {Food=bagel sandwiches}} - i need {FoodRequest=2 {Food=eggs}}, {Food=toast}, and an {FoodRequest={Food=egg sandwich} {SpecialRequest=with extra bacon}} diff --git a/skills/src/csharp/experimental/hospitalityskill/Dialogs/RoomServiceDialog.cs b/skills/src/csharp/experimental/hospitalityskill/Dialogs/RoomServiceDialog.cs index 3ace0543ff..208db3e036 100644 --- a/skills/src/csharp/experimental/hospitalityskill/Dialogs/RoomServiceDialog.cs +++ b/skills/src/csharp/experimental/hospitalityskill/Dialogs/RoomServiceDialog.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; diff --git a/skills/src/csharp/experimental/hospitalityskill/Responses/RoomService/RoomServiceResponses.cs b/skills/src/csharp/experimental/hospitalityskill/Responses/RoomService/RoomServiceResponses.cs index b3c83e6130..f6de468392 100644 --- a/skills/src/csharp/experimental/hospitalityskill/Responses/RoomService/RoomServiceResponses.cs +++ b/skills/src/csharp/experimental/hospitalityskill/Responses/RoomService/RoomServiceResponses.cs @@ -16,7 +16,6 @@ public class RoomServiceResponses : IResponseIdCollection public const string ChooseOneMenu = "ChooseOneMenu"; public const string FoodOrder = "FoodOrder"; public const string RetryFoodOrder = "RetryFoodOrder"; - public const string ListOrder = "ListOrder"; public const string ItemsNotAvailable = "ItemsNotAvailable"; public const string AddMore = "AddMore"; public const string ConfirmOrder = "ConfirmOrder"; diff --git a/skills/src/csharp/experimental/hospitalityskill/Responses/RoomService/RoomServiceResponses.json b/skills/src/csharp/experimental/hospitalityskill/Responses/RoomService/RoomServiceResponses.json index c3d709546e..99f16e779a 100644 --- a/skills/src/csharp/experimental/hospitalityskill/Responses/RoomService/RoomServiceResponses.json +++ b/skills/src/csharp/experimental/hospitalityskill/Responses/RoomService/RoomServiceResponses.json @@ -35,15 +35,6 @@ ], "inputHint": "expectingInput" }, - "ListOrder": { - "replies": [ - { - "text": "Here is your current order:", - "speak": "Here is your current order:" - } - ], - "inputHint": "ignoringInput" - }, "ItemsNotAvailable": { "replies": [ { diff --git a/skills/src/csharp/experimental/hospitalityskill/Services/HotelService.cs b/skills/src/csharp/experimental/hospitalityskill/Services/HotelService.cs index 78a39c068e..e4ecd318ac 100644 --- a/skills/src/csharp/experimental/hospitalityskill/Services/HotelService.cs +++ b/skills/src/csharp/experimental/hospitalityskill/Services/HotelService.cs @@ -73,6 +73,7 @@ public MenuItem CheckMenuItemAvailability(string item) } } + // gets requested menu details public Menu GetMenu(string menuType) { using (var r = new StreamReader(typeof(HotelService).Assembly.GetManifestResourceStream(_menuFilePath))) From 963f09968f928e6fc43ae4a89e720a917ecdcf36 Mon Sep 17 00:00:00 2001 From: litofish Date: Thu, 15 Aug 2019 15:52:02 -0700 Subject: [PATCH 07/23] Move requested item availability check to hotel service and add adaptive card for showing item requests --- .../Content/RequestItemCard.json | 87 ++++++++++++++++ .../Content/RoomItemCard.json | 34 +++++++ .../hospitalityskill/Data/AvailableItems.json | 99 ++++++++++++------- .../Deployment/Resources/LU/en/Hospitality.lu | 3 +- .../Dialogs/RequestItemDialog.cs | 47 ++++----- .../hospitalityskill/HospitalitySkill.csproj | 4 + .../hospitalityskill/Models/RoomItem.cs | 13 +++ .../RequestItem/RequestItemResponses.json | 4 +- .../Shared/HospitalityStrings.Designer.cs | 4 +- .../Responses/Shared/HospitalityStrings.resx | 4 +- .../hospitalityskill/Services/HotelService.cs | 28 ++++++ .../Services/IHotelService.cs | 3 + 12 files changed, 261 insertions(+), 69 deletions(-) create mode 100644 skills/src/csharp/experimental/hospitalityskill/Content/RequestItemCard.json create mode 100644 skills/src/csharp/experimental/hospitalityskill/Content/RoomItemCard.json create mode 100644 skills/src/csharp/experimental/hospitalityskill/Models/RoomItem.cs diff --git a/skills/src/csharp/experimental/hospitalityskill/Content/RequestItemCard.json b/skills/src/csharp/experimental/hospitalityskill/Content/RequestItemCard.json new file mode 100644 index 0000000000..bf394ae35e --- /dev/null +++ b/skills/src/csharp/experimental/hospitalityskill/Content/RequestItemCard.json @@ -0,0 +1,87 @@ +{ + "type": "AdaptiveCard", + "version": "1.0", + "id": "RequestItemCard", + "body": [ + { + "type": "Container", + "backgroundImage": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAmwAAADVCAIAAABlrTvmAAAACXBIWXMAABYlAAAWJQFJUiTwAAAG0mlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDUgNzkuMTYzNDk5LCAyMDE4LzA4LzEzLTE2OjQwOjIyICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoTWFjaW50b3NoKSIgeG1wOkNyZWF0ZURhdGU9IjIwMTktMDMtMjJUMTc6MDM6NDAtMDc6MDAiIHhtcDpNb2RpZnlEYXRlPSIyMDE5LTAzLTIyVDE3OjE2OjU2LTA3OjAwIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDE5LTAzLTIyVDE3OjE2OjU2LTA3OjAwIiBkYzpmb3JtYXQ9ImltYWdlL3BuZyIgcGhvdG9zaG9wOkNvbG9yTW9kZT0iMyIgcGhvdG9zaG9wOklDQ1Byb2ZpbGU9InNSR0IgSUVDNjE5NjYtMi4xIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjY0ZGY4YjdiLTM2MTYtNDRkNy04MTI3LTgyNzk4NmUyODk3ZSIgeG1wTU06RG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOmExNDU0NmE3LWZhODMtMjc0Mi1hNWU0LWIxMzYzNjQ2NWU5NyIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOjhlMDlmYmE5LTE2ZjktNGZiNC05MzdhLThkOTYzMGMxOTQyNiI+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249ImNyZWF0ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6OGUwOWZiYTktMTZmOS00ZmI0LTkzN2EtOGQ5NjMwYzE5NDI2IiBzdEV2dDp3aGVuPSIyMDE5LTAzLTIyVDE3OjAzOjQwLTA3OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoTWFjaW50b3NoKSIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6Yzc5ODg2ZTQtZTg3Zi00MmMzLWI5ZjYtN2FkMDg3YTlkOTg4IiBzdEV2dDp3aGVuPSIyMDE5LTAzLTIyVDE3OjE2OjU2LTA3OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoTWFjaW50b3NoKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6NjRkZjhiN2ItMzYxNi00NGQ3LTgxMjctODI3OTg2ZTI4OTdlIiBzdEV2dDp3aGVuPSIyMDE5LTAzLTIyVDE3OjE2OjU2LTA3OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoTWFjaW50b3NoKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7bJaRFAAAIbUlEQVR4nO3dy27bSAKGUZKWFCfuLKYH6N712/XzzTvNE8ykJzfHkmZRhpN2ZItkFVkXnrOUNj9AgB9IkVD/57/+3QHQuk9fvv33w6fcK6Z5ux/2N/3Fry5/uroh9wAAFtdYQcshogCNU9DliChAyxR0USIK0CwFXZqIArRJQVcgogANUtB1iChAaxR0NSIK0BQFXZOIArRDQVcmogCNUND1iShACxQ0CxEFqJ6C5iKiAHVT0IxEFKBiCpqXiALUSkGzE1GAKtVY0Nu2CtqJKECNKi3ooa2CdiIKUB0FLYeIAtREQYsiogDVUNDSiChAHRS0QCIKUAEFLZOIApROQYslogBFU9CSiShAuRS0cCIKUCgFLZ+IApTo/ttDjQXdDxsqaCeiAAU6nc//+fA594ppNljQTkQBCvTx0/3xeMq9YoJtFrQTUYACff5yn3vCBJstaCeiAKX59nCs6DJ0ywXtRBSgNF++PuSeMNbGC9qJKEBparkMVdBORAFKczxVEFEFDUQUgGkU9ImIAjCBgv5IRAHKctjvck94kYI+I6IAZdnvCj0zK+jPCj1UAJt1OJR4JaqgF4koQFmGvn97e8i94m8U9CUiClCc93dvck/4TkFfIaIAxbm5GX65u829ousU9BoRBSjR+7s3u91N3g0KepWIAhTqn/+4y9hRBR1DRAEKNfR9ro4q6EgiClCuLB1V0PFEFKBoK3dUQScRUYDSrdbR2/2wU9ApRBSgAit0VEFnEFGAOizaUQWdR0QBqrFQRxV0NhEFqEnyjnqSKIaIAlQmYUcVNJKIAtQnSUcVNJ6IAlQpsqMKmoSIAtRqdkcVNBURBajYjI4qaEIiClC3SR1V0LREFKB6IzvqfdDkRBSgBVc7qqBLEFGARrzSUQVdiIgCtONiRxV0OSIK0JRnHVXQRYkoQGueOqqgSxNRgAYNff/H7+/fHXa5hzRORAEadHcYbnfDb7/eHZb8H29EFKA1d4fhcNN3XTcMvY4uSkQBmvJU0EBHFyWiAO14VtBAR5cjogCNuFjQQEcXIqIALXi3f7GggY4uQUQBqvduP7zZXX8fVEeTE1GAuo0saKCjaYkoQMUmFTTQ0YREFKBWMwoa6GgqIgpQpdkFDXQ0CREFqE9kQQMdjSeiAJVJUtBARyOJKEBNrr4POpWOxhBRgGokL2igo7OJKEAdFipooKPziChABRYtaKCjM4goQOlWKGigo1OJKEDRVitooKOTiChAuVYuaKCj44koQKGyFDTQ0ZFEFKBEGQsa6OgYIgpQnOwFDXT0KhEFKEshBQ109HUiClCQogoa6OgrRBSgFAUWNNDRl4goQBGKLWigoxeJKEB+hRc00NGfiShAZlUUNNDRZ0QUIKeKChro6I9EFCCb6goa6OgTEQXIo9KCBjoaiChABlUXNNDRTkQB1tdAQQMdFVGAVTVT0GDjHRVRgPU0VtBgyx0VUYCVNFnQYLMdFVGANTRc0GCbHRVRgMU1X9Bggx0VUYBlbaSgwdY6KqIAC9pUQYNNdVREAZaywYIG2+moiAIsYrMFDYah/+3XX5rvqIgCpLfxggZb6KiIAiR2u+sVNGi+oyIKkNJu6G93Tq3ftd1RRxogpbd759XnGu6ogw2QzOGmdx/3olY7KqIAyewl9GVNdlREAdLo+24/iOhr2uuoiAKkcdMr6HWNdVREAdJwK3ekljoqogBpuBAdr5mOiigAGbTRUREFSON4zr2gNg10VEQB0jiJ6HS1d1REAdI4ns6n3BtqVHVHRRQgmQe3dGept6MiCpDMVxGdq9KOiihAMsfT+V5H56qxoyIKkNLnh5NfRmerrqMiCpDS+dx9vNfR+erqqIgCJHY8nXU0RkUdFVGA9HQ0Ui0dFVGARehopCo6KqIAS3nsqMd15yq/oyIKsKDj6fzxm47OV3hHRRRgWToaqeSOiijA4nQ0UrEdFVGANYSOyuhsZXZURAFWcjyd/3evo/MV2FERBViPjkYqraMiCrAqHY0UOrovo6MiCrA2HY00DP3vZXRURAEy0NFIhXRURAHy0NFIJXRURAGy0dFI2TsqogA56WikvB0VUYDMdDRSxo6KKEB+OhopV0dFFKAIOhopS0dFFKAUOhpp/Y6KKEBBdDTSyh0VUYCy6GikNTsqogDF0dFIq3VURAFKpKOR1umoiAIUKnQ094qKrdBREQUo1/F0/ktHIyzdUREFKJqORlq0oyIKUDodjfTY0X36joooQAV0NNJCHRVRgDroaKQlOiqiANXQ0UjJOyqiADUJHT17gXSutB0VUYDK6GikhB0VUYD66GikVB0VUYAq6Wik0NFDXEdFFKBWOhopvqMiClAxHY0U2VERBaibjkaK6aiIAlRPRyPNfs5IRAFaoKOR5nVURAEaoaORZnRURAHaoaORpv4+KqIATTmezn991dH5JnVURAFaczzraJTxHRVRgAbpaKSRHRVRgDbpaKQxHRVRgGbpaKSrHRVRgJbpaKTXOyqiAI3T0UivdFREAdqno5Fe6qiIAmyCjka62FERBdiK0NGTjs71c0dFFGBDdDTSs46KKMC26GikHzsqogCbo6ORnjoqogBbpKORQkdFFGCjdDTSMPQiCrBdOhpJRAE2zfujMUQUYOt0dDYRBUBHZxJRALpOR2cRUQAePXY094yKiCgA3+noJCIKwN88nHR0LBEF4DkdHUlEAbhAR8cQUQAu09GrRBSAF+no60QUgNfo6CtEFIArdPQlIgrAdTp6kYgCMIqO/kxEARhLR58RUQAm0NEfiSgA0+joExEFYDIdDUQUgDl0tBNRAGbTUREFYL6Nd1REAYiy5Y6KKACxNttREQUggW12VEQBSOOxo1sKqYgCkMzD6fxhSx39P2CJeSCLm9hfAAAAAElFTkSuQmCC", + "items": [ + { + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "items": [ + { + "type": "Image", + "id": "icon", + "horizontalAlignment": "Center", + "url": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyNCAyMSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTI0IDEyVjIxSDIyLjVWMTkuNUgxLjVWMjFIMFYxMkgxLjVWMTMuMzI0Mkw0LjUgNy4zMjQyMlYwSDZWMS41SDE4VjBIMTkuNVY0LjVDMTkuNzEwOSA0LjUgMTkuOTA2MiA0LjUzOTA2IDIwLjA4NTkgNC42MTcxOUMyMC4yNjU2IDQuNjk1MzEgMjAuNDIxOSA0LjgwNDY5IDIwLjU1NDcgNC45NDUzMUMyMC42OTUzIDUuMDc4MTIgMjAuODA0NyA1LjIzNDM4IDIwLjg4MjggNS40MTQwNkMyMC45NjA5IDUuNTkzNzUgMjEgNS43ODkwNiAyMSA2VjlDMjEgOS4zMDQ2OSAyMC45MTggOS41NzgxMiAyMC43NTM5IDkuODIwMzFMMjIuNSAxMy4zMjQyVjEySDI0Wk0xMy41IDZWOUgxOS41VjZIMTMuNVpNNiAzVjcuNUgxMlY2QzEyIDUuNzg5MDYgMTIuMDM5MSA1LjU5Mzc1IDEyLjExNzIgNS40MTQwNkMxMi4xOTUzIDUuMjM0MzggMTIuMzAwOCA1LjA3ODEyIDEyLjQzMzYgNC45NDUzMUMxMi41NzQyIDQuODA0NjkgMTIuNzM0NCA0LjY5NTMxIDEyLjkxNDEgNC42MTcxOUMxMy4wOTM4IDQuNTM5MDYgMTMuMjg5MSA0LjUgMTMuNSA0LjVIMThWM0g2Wk0zLjA5Mzc1IDEzLjVIMjAuOTA2MkwxOS40MDYyIDEwLjVIMTMuNUMxMy4yODkxIDEwLjUgMTMuMDkzOCAxMC40NjA5IDEyLjkxNDEgMTAuMzgyOEMxMi43MzQ0IDEwLjMwNDcgMTIuNTc0MiAxMC4xOTkyIDEyLjQzMzYgMTAuMDY2NEMxMi4zMDA4IDkuOTI1NzggMTIuMTk1MyA5Ljc2NTYyIDEyLjExNzIgOS41ODU5NEMxMi4wMzkxIDkuNDA2MjUgMTIgOS4yMTA5NCAxMiA5SDUuMzMyMDNMMy4wOTM3NSAxMy41Wk0yMi41IDE4VjE1SDEuNVYxOEgyMi41WiIgZmlsbD0id2hpdGUiLz4KPC9zdmc+Cg==", + "size": "Small", + "width": "30px", + "height": "30px" + } + ], + "width": "auto" + }, + { + "type": "Column", + "verticalContentAlignment": "Center", + "items": [ + { + "type": "TextBlock", + "id": "title", + "size": "Large", + "weight": "Bolder", + "color": "Light", + "text": "Your Item Requests" + } + ], + "width": "stretch" + } + ] + } + ] + }, + { + "type": "Container", + "items": [ + { + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "items": [ + { + "type": "TextBlock", + "weight": "Bolder", + "text": "Qty", + "horizontalAlignment": "Center" + } + ], + "width": "40px" + }, + { + "type": "Column", + "items": [ + { + "type": "TextBlock", + "weight": "Bolder", + "text": "Item" + } + ], + "width": "stretch" + } + ] + } + ] + }, + { + "type": "Container", + "id": "items", + "items": [] + } + ], + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json" +} \ No newline at end of file diff --git a/skills/src/csharp/experimental/hospitalityskill/Content/RoomItemCard.json b/skills/src/csharp/experimental/hospitalityskill/Content/RoomItemCard.json new file mode 100644 index 0000000000..729f33e310 --- /dev/null +++ b/skills/src/csharp/experimental/hospitalityskill/Content/RoomItemCard.json @@ -0,0 +1,34 @@ +{ + "type": "AdaptiveCard", + "version": "1.0", + "id": "RoomItemCard", + "body": [ + { + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "items": [ + { + "type": "TextBlock", + "text": "{Quantity}", + "horizontalAlignment": "Center" + } + ], + "width": "40px" + }, + { + "type": "Column", + "items": [ + { + "type": "TextBlock", + "text": "{Item}" + } + ], + "width": "stretch" + } + ] + } + ], + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json" +} \ No newline at end of file diff --git a/skills/src/csharp/experimental/hospitalityskill/Data/AvailableItems.json b/skills/src/csharp/experimental/hospitalityskill/Data/AvailableItems.json index c4443c7e54..4e40df6f7c 100644 --- a/skills/src/csharp/experimental/hospitalityskill/Data/AvailableItems.json +++ b/skills/src/csharp/experimental/hospitalityskill/Data/AvailableItems.json @@ -1,33 +1,66 @@ - [ - "towel", - "towels", - "hand towels", - "handtowel", - "hand towel", - "shampoo", - "conditioner", - "body wash", - "body soap", - "soap bar", - "soap", - "lotion", - "moisturizer", - "pillow", - "pillows", - "foam pillow", - "foam pillows", - "down pillow", - "down pillows", - "hair dryer", - "comb", - "toothbrush", - "toothbrushes", - "toothpaste", - "toilet paper", - "toilet roll", - "toilet paper roll", - "coffee", - "coffees", - "tissues", - "tissue box" - ] \ No newline at end of file +[ + { + "item": "Towel", + "names": [ "towel", "towels", "bath towel", "bath towels" ] + }, + { + "item": "Hand Towel", + "names": [ "hand towel", "hand towels", "handtowel", "handtowels" ] + }, + { + "item": "Shampoo", + "names": [ "shampoo", "shampoos", "shampoo bottle", "shampoo bottles", "bottle of shampoo", "bottles of shampoo" ] + }, + { + "item": "Conditioner", + "names": [ "conditioner", "conditioners", "conditioner bottle", "conditioner bottles", "bottle of conditioner", "bottles of conditioner" ] + }, + { + "item": "Body Wash", + "names": [ "body wash", "bodywash", "body soap", "bodysoap", "bottle of body wash", "body washes", "body soaps", "bottles of body wash" ] + }, + { + "item": "Soap Bar", + "names": [ "bar of soap", "soap bar", "bars of soap", "soap bars", "soap", "soaps" ] + }, + { + "item": "Lotion", + "names": [ "lotion", "lotions", "lotion bottle", "lotion bottles", "bottle of lotion", "bottles of lotion", "moisturizer", "moisturizers" ] + }, + { + "item": "Pillow", + "names": [ "pillow", "pillows" ] + }, + { + "item": "Foam Pillow", + "names": [ "foam pillow", "foam pillows" ] + }, + { + "item": "Feather Pillow", + "names": [ "feather pillow", "feather pillows", "down pillow", "down pillows" ] + }, + { + "item": "Hair Dryer", + "names": [ "hair dryer", "hairdryer", "hair dryers" ] + }, + { + "item": "Comb", + "names": [ "comb", "brush", "hair brush", "hairbrush" ] + }, + { + "item": "Toothbrush", + "names": [ "toothbrush", "toothbrushes", "tooth brush", "tooth brushes" ] + }, + { + "item": "Toothpaste", + "names": [ "toothpaste", "toothpastes", "tubes of toothpaste", "tube of toothpaste", "tooth paste", "tooth pastes" ] + }, + { + "item": "Toilet Paper Roll", + "names": [ "toilet paper", "toilet papers", "toilet paper roll", "toilet paper rolls", "toilet roll", "toilet rolls" ] + }, + { + "item": "Tissue Box", + "names": [ "tissue box", "tissue boxes", "tissues", "tissue", "box of tissue", "box of tissues" ] + } +] diff --git a/skills/src/csharp/experimental/hospitalityskill/Deployment/Resources/LU/en/Hospitality.lu b/skills/src/csharp/experimental/hospitalityskill/Deployment/Resources/LU/en/Hospitality.lu index 782458e479..b5b30018d7 100644 --- a/skills/src/csharp/experimental/hospitalityskill/Deployment/Resources/LU/en/Hospitality.lu +++ b/skills/src/csharp/experimental/hospitalityskill/Deployment/Resources/LU/en/Hospitality.lu @@ -1,4 +1,4 @@ -> ! Automatically generated by [LUDown CLI](https://github.com/Microsoft/botbuilder-tools/tree/master/Ludown), Thu Aug 15 2019 13:29:21 GMT-0700 (Pacific Daylight Time) +> ! Automatically generated by [LUDown CLI](https://github.com/Microsoft/botbuilder-tools/tree/master/Ludown), Thu Aug 15 2019 15:40:54 GMT-0700 (Pacific Daylight Time) > ! Source LUIS JSON file: stdin @@ -134,6 +134,7 @@ - can i get {ItemRequest=2 {Item=razors}} - can i get {ItemRequest=2 {Item=razors}} and {ItemRequest=3 {Item=towels}} - can i get {ItemRequest=2 {Item=shampoo}} bottles +- can i get {ItemRequest=2 {Item=toilet paper rolls}} and a {Item=nail clipper} - can i get {ItemRequest=5 {Item=blankets}}, {ItemRequest=2 {Item=toothbrushes}}, and {ItemRequest=2 {Item=hand towels}} - can i get a different {Item=pillow} and {Item=sheets} - can i get a {Item=razor} and a {Item=towel} diff --git a/skills/src/csharp/experimental/hospitalityskill/Dialogs/RequestItemDialog.cs b/skills/src/csharp/experimental/hospitalityskill/Dialogs/RequestItemDialog.cs index 26cc5f5917..1c8e008e4d 100644 --- a/skills/src/csharp/experimental/hospitalityskill/Dialogs/RequestItemDialog.cs +++ b/skills/src/csharp/experimental/hospitalityskill/Dialogs/RequestItemDialog.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Globalization; -using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -11,16 +9,13 @@ using Microsoft.Bot.Builder; using Microsoft.Bot.Builder.Dialogs; using Microsoft.Bot.Builder.Solutions.Responses; -using Newtonsoft.Json; using static Luis.HospitalityLuis._Entities; namespace HospitalitySkill.Dialogs { public class RequestItemDialog : HospitalityDialogBase { - private const string AvailableItemsFileName = "AvailableItems.json"; private readonly HotelService _hotelService; - private readonly string _availableItemsFilePath; public RequestItemDialog( BotSettings settings, @@ -42,11 +37,6 @@ public RequestItemDialog( _hotelService = hotelService; - _availableItemsFilePath = typeof(RequestItemDialog).Assembly - .GetManifestResourceNames() - .Where(x => x.Contains(AvailableItemsFileName)) - .First(); - AddDialog(new WaterfallDialog(nameof(RequestItemDialog), requestItem)); AddDialog(new TextPrompt(DialogIds.ItemPrompt, ValidateItemPrompt)); AddDialog(new ConfirmPrompt(DialogIds.GuestServicesPrompt, ValidateGuestServicesPrompt)); @@ -126,12 +116,18 @@ private async Task ItemRequest(WaterfallStepContext sc, Cancel foreach (var itemRequest in convState.ItemList.ToList()) { - if (!CheckItemAvailability(itemRequest.Item[0])) + var roomItem = _hotelService.CheckRoomItemAvailability(itemRequest.Item[0]); + + if (roomItem == null) { // specific item is not available notAvailable.Add(itemRequest); convState.ItemList.Remove(itemRequest); } + else + { + itemRequest.Item[0] = roomItem.Item; + } } if (notAvailable.Count > 0) @@ -139,7 +135,7 @@ private async Task ItemRequest(WaterfallStepContext sc, Cancel var reply = ResponseManager.GetResponse(RequestItemResponses.ItemNotAvailable).Text; foreach (var itemRequest in notAvailable) { - reply += Environment.NewLine + "- " + CultureInfo.CurrentCulture.TextInfo.ToTitleCase(itemRequest.Item[0]); + reply += Environment.NewLine + "- " + itemRequest.Item[0]; } await sc.Context.SendActivityAsync(reply); @@ -153,16 +149,6 @@ private async Task ItemRequest(WaterfallStepContext sc, Cancel return await sc.NextAsync(); } - private bool CheckItemAvailability(string item) - { - using (var r = new StreamReader(typeof(RequestItemDialog).Assembly.GetManifestResourceStream(_availableItemsFilePath))) - { - string json = r.ReadToEnd(); - string[] items = JsonConvert.DeserializeObject(json); - return Array.Exists(items, x => x == item); - } - } - private async Task ValidateGuestServicesPrompt(PromptValidatorContext promptContext, CancellationToken cancellationToken) { if (promptContext.Recognized.Succeeded) @@ -185,21 +171,24 @@ private async Task EndDialog(WaterfallStepContext sc, Cancella if (convState.ItemList.Count > 0) { - string reply = ResponseManager.GetResponse(RequestItemResponses.ItemsRequested).Text; + List roomItems = new List(); + foreach (var itemRequest in convState.ItemList) { - if (itemRequest.number == null) + var roomItem = new RoomItem { - itemRequest.number = new double[] { 1.0 }; - } + Item = itemRequest.Item[0], + Quantity = itemRequest.number == null ? 1 : (int)itemRequest.number[0] + }; - reply += Environment.NewLine + "- " + CultureInfo.CurrentCulture.TextInfo.ToTitleCase(itemRequest.Item[0]) + string.Format(" ({0})", itemRequest.number[0].ToString()); + roomItems.Add(new Card("RoomItemCard", roomItem)); } await _hotelService.RequestItems(convState.ItemList); - // if at least one item was available send this reply - await sc.Context.SendActivityAsync(reply); + // if at least one item was available send this card reply + await sc.Context.SendActivityAsync(ResponseManager.GetCardResponse(null, new Card("RequestItemCard"), null, "items", roomItems)); + await sc.Context.SendActivityAsync(ResponseManager.GetResponse(RequestItemResponses.ItemsRequested)); } return await sc.EndDialogAsync(); diff --git a/skills/src/csharp/experimental/hospitalityskill/HospitalitySkill.csproj b/skills/src/csharp/experimental/hospitalityskill/HospitalitySkill.csproj index 16710822d9..e539f5fcda 100644 --- a/skills/src/csharp/experimental/hospitalityskill/HospitalitySkill.csproj +++ b/skills/src/csharp/experimental/hospitalityskill/HospitalitySkill.csproj @@ -11,7 +11,9 @@ + + @@ -39,7 +41,9 @@ + + diff --git a/skills/src/csharp/experimental/hospitalityskill/Models/RoomItem.cs b/skills/src/csharp/experimental/hospitalityskill/Models/RoomItem.cs new file mode 100644 index 0000000000..186011c72e --- /dev/null +++ b/skills/src/csharp/experimental/hospitalityskill/Models/RoomItem.cs @@ -0,0 +1,13 @@ +using Microsoft.Bot.Builder.Solutions.Responses; + +namespace HospitalitySkill.Models +{ + public class RoomItem : ICardData + { + public string Item { get; set; } + + public string[] Names { get; set; } + + public int Quantity { get; set; } + } +} diff --git a/skills/src/csharp/experimental/hospitalityskill/Responses/RequestItem/RequestItemResponses.json b/skills/src/csharp/experimental/hospitalityskill/Responses/RequestItem/RequestItemResponses.json index 4a2fb14e3a..ea3acd4801 100644 --- a/skills/src/csharp/experimental/hospitalityskill/Responses/RequestItem/RequestItemResponses.json +++ b/skills/src/csharp/experimental/hospitalityskill/Responses/RequestItem/RequestItemResponses.json @@ -56,8 +56,8 @@ "ItemsRequested": { "replies": [ { - "text": "The following items will be brought to your room shortly: ", - "speak": "The following items will be brought to your room shortly: " + "text": "These items will be brought to your room shortly.", + "speak": "These items will be brought to your room shortly." } ], "inputHint": "acceptingInput" diff --git a/skills/src/csharp/experimental/hospitalityskill/Responses/Shared/HospitalityStrings.Designer.cs b/skills/src/csharp/experimental/hospitalityskill/Responses/Shared/HospitalityStrings.Designer.cs index 5688c55289..57a84f8c69 100644 --- a/skills/src/csharp/experimental/hospitalityskill/Responses/Shared/HospitalityStrings.Designer.cs +++ b/skills/src/csharp/experimental/hospitalityskill/Responses/Shared/HospitalityStrings.Designer.cs @@ -61,7 +61,7 @@ internal HospitalityStrings() { } /// - /// Looks up a localized string similar to Reservation details. + /// Looks up a localized string similar to Reservation Details. /// public static string ReservationDetails { get { @@ -70,7 +70,7 @@ public static string ReservationDetails { } /// - /// Looks up a localized string similar to Updated reservation details . + /// Looks up a localized string similar to Updated Reservation Details . /// public static string UpdateReservation { get { diff --git a/skills/src/csharp/experimental/hospitalityskill/Responses/Shared/HospitalityStrings.resx b/skills/src/csharp/experimental/hospitalityskill/Responses/Shared/HospitalityStrings.resx index d7ef6265e5..368a725846 100644 --- a/skills/src/csharp/experimental/hospitalityskill/Responses/Shared/HospitalityStrings.resx +++ b/skills/src/csharp/experimental/hospitalityskill/Responses/Shared/HospitalityStrings.resx @@ -118,9 +118,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Reservation details + Reservation Details - Updated reservation details + Updated Reservation Details \ No newline at end of file diff --git a/skills/src/csharp/experimental/hospitalityskill/Services/HotelService.cs b/skills/src/csharp/experimental/hospitalityskill/Services/HotelService.cs index e4ecd318ac..d66810dc8e 100644 --- a/skills/src/csharp/experimental/hospitalityskill/Services/HotelService.cs +++ b/skills/src/csharp/experimental/hospitalityskill/Services/HotelService.cs @@ -14,7 +14,10 @@ namespace HospitalitySkill.Services public class HotelService : IHotelService { private const string RoomServiceMenuFileName = "RoomServiceMenu.json"; + private const string AvailableItemsFileName = "AvailableItems.json"; + private readonly string _menuFilePath; + private readonly string _availableItemsFilePath; public HotelService() { @@ -22,6 +25,11 @@ public HotelService() .GetManifestResourceNames() .Where(x => x.Contains(RoomServiceMenuFileName)) .First(); + + _availableItemsFilePath = typeof(HotelService).Assembly + .GetManifestResourceNames() + .Where(x => x.Contains(AvailableItemsFileName)) + .First(); } public async Task GetLateCheckOutAsync() @@ -49,6 +57,26 @@ public async Task RequestItems(List items) return await Task.FromResult(true); } + public RoomItem CheckRoomItemAvailability(string item) + { + using (var r = new StreamReader(typeof(HotelService).Assembly.GetManifestResourceStream(_availableItemsFilePath))) + { + string json = r.ReadToEnd(); + RoomItem[] roomItems = JsonConvert.DeserializeObject(json); + + // check all item names + foreach (var roomItem in roomItems) + { + if (Array.Exists(roomItem.Names, x => string.Equals(x, item, StringComparison.CurrentCultureIgnoreCase))) + { + return roomItem; + } + } + + return null; + } + } + // returns full name of menu item if found public MenuItem CheckMenuItemAvailability(string item) { diff --git a/skills/src/csharp/experimental/hospitalityskill/Services/IHotelService.cs b/skills/src/csharp/experimental/hospitalityskill/Services/IHotelService.cs index 27e9755fe9..87bfed40eb 100644 --- a/skills/src/csharp/experimental/hospitalityskill/Services/IHotelService.cs +++ b/skills/src/csharp/experimental/hospitalityskill/Services/IHotelService.cs @@ -20,6 +20,9 @@ public interface IHotelService // request items to be brought Task RequestItems(List items); + // check item request availability + RoomItem CheckRoomItemAvailability(string item); + // check availability of a room service request MenuItem CheckMenuItemAvailability(string item); From cc87ba9840360f674fd32d6f449b18053f654f7f Mon Sep 17 00:00:00 2001 From: litofish Date: Thu, 15 Aug 2019 16:22:36 -0700 Subject: [PATCH 08/23] update manifest --- .../hospitalityskill/manifestTemplate.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/skills/src/csharp/experimental/hospitalityskill/manifestTemplate.json b/skills/src/csharp/experimental/hospitalityskill/manifestTemplate.json index 8f89927bf7..b7ee87b1b3 100644 --- a/skills/src/csharp/experimental/hospitalityskill/manifestTemplate.json +++ b/skills/src/csharp/experimental/hospitalityskill/manifestTemplate.json @@ -89,6 +89,23 @@ ] } } + }, + { + "id": "hospitalitySkill_roomService", + "definition": { + "description": "Order room service.", + "slots": [], + "triggers": { + "utteranceSources": [ + { + "locale": "en", + "source": [ + "Hospitality#RoomService" + ] + } + ] + } + } } ] } \ No newline at end of file From eccb7fc98cf9f0d6d2b26df5211c19812ea8e9be Mon Sep 17 00:00:00 2001 From: litofish Date: Fri, 16 Aug 2019 11:48:40 -0700 Subject: [PATCH 09/23] Adding updated hospitality skill --- .../Content/NewUserGreeting.json | 2 +- .../HospitalitySample/Services/DispatchLuis.cs | 6 +++--- solutions/HospitalitySample/skills.json | 17 +++++++++++++++++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/solutions/HospitalitySample/Content/NewUserGreeting.json b/solutions/HospitalitySample/Content/NewUserGreeting.json index 1ebffe0758..12c41afb01 100644 --- a/solutions/HospitalitySample/Content/NewUserGreeting.json +++ b/solutions/HospitalitySample/Content/NewUserGreeting.json @@ -25,7 +25,7 @@ "size": "Large", "weight": "Bolder", "color": "Light", - "text": "Hi, I'm **your** Virtual Assistant", + "text": "Hi, I'm **your** Hospitality Assistant", "wrap": true }, { diff --git a/solutions/HospitalitySample/Services/DispatchLuis.cs b/solutions/HospitalitySample/Services/DispatchLuis.cs index 81ce1dfe50..45f853e332 100644 --- a/solutions/HospitalitySample/Services/DispatchLuis.cs +++ b/solutions/HospitalitySample/Services/DispatchLuis.cs @@ -19,11 +19,11 @@ public enum Intent { q_Chitchat, q_Faq, q_hotel_FAQ, - hospitalitySkill, restaurantBookingSkill, newsSkill, pointOfInterestSkill, WeatherSkill, + hospitalitySkill, None }; public Dictionary Intents; @@ -31,20 +31,20 @@ public enum Intent { public class _Entities { // Simple entities - public string[] Item; public string[] topic; public string[] site; public string[] KEYWORD; public string[] ADDRESS; + public string[] Item; // Instance public class _Instance { - public InstanceData[] Item; public InstanceData[] topic; public InstanceData[] site; public InstanceData[] KEYWORD; public InstanceData[] ADDRESS; + public InstanceData[] Item; } [JsonProperty("$instance")] public _Instance _instance; diff --git a/solutions/HospitalitySample/skills.json b/solutions/HospitalitySample/skills.json index 9f24956d26..b78e5aa3c6 100644 --- a/solutions/HospitalitySample/skills.json +++ b/solutions/HospitalitySample/skills.json @@ -92,6 +92,23 @@ ] } } + }, + { + "id": "hospitalitySkill_roomService", + "definition": { + "description": "Order room service.", + "slots": [], + "triggers": { + "utteranceSources": [ + { + "locale": "en", + "source": [ + "Hospitality#RoomService" + ] + } + ] + } + } } ] }, From a8c0d95e25c1ad872aede22b2b75f217dd0c7f2b Mon Sep 17 00:00:00 2001 From: litofish Date: Fri, 16 Aug 2019 15:58:24 -0700 Subject: [PATCH 10/23] small changes to sample --- solutions/HospitalitySample/HospitalitySample.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/solutions/HospitalitySample/HospitalitySample.csproj b/solutions/HospitalitySample/HospitalitySample.csproj index b93fc574b8..a685a4482e 100644 --- a/solutions/HospitalitySample/HospitalitySample.csproj +++ b/solutions/HospitalitySample/HospitalitySample.csproj @@ -3,6 +3,7 @@ netcoreapp2.2 NU1701 + 96a10d5f-5616-41d3-a131-a310c90072ff From 7e03a916dbcfbb6efb7d1361afcd7d6e83025630 Mon Sep 17 00:00:00 2001 From: litofish Date: Fri, 16 Aug 2019 16:02:27 -0700 Subject: [PATCH 11/23] restaurant booking fixes --- .../experimental/restaurantbooking/Dialogs/BookingDialog.cs | 2 +- .../csharp/experimental/restaurantbooking/Dialogs/MainDialog.cs | 2 +- .../experimental/restaurantbooking/Dialogs/SkillDialogBase.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/skills/src/csharp/experimental/restaurantbooking/Dialogs/BookingDialog.cs b/skills/src/csharp/experimental/restaurantbooking/Dialogs/BookingDialog.cs index 549711f378..aa2ff88e34 100644 --- a/skills/src/csharp/experimental/restaurantbooking/Dialogs/BookingDialog.cs +++ b/skills/src/csharp/experimental/restaurantbooking/Dialogs/BookingDialog.cs @@ -92,7 +92,7 @@ public BookingDialog( // This would be passed from the Virtual Assistant moving forward var tokens = new StringDictionary { - { "UserName", state.Name ?? "Unknown" } + { "UserName", state.Name ?? string.Empty } }; // Start the flow diff --git a/skills/src/csharp/experimental/restaurantbooking/Dialogs/MainDialog.cs b/skills/src/csharp/experimental/restaurantbooking/Dialogs/MainDialog.cs index 2e34afdeaa..ad145e4280 100644 --- a/skills/src/csharp/experimental/restaurantbooking/Dialogs/MainDialog.cs +++ b/skills/src/csharp/experimental/restaurantbooking/Dialogs/MainDialog.cs @@ -69,7 +69,7 @@ public MainDialog( var localeConfig = _services.CognitiveModelSets[locale]; // Get skill LUIS model from configuration - localeConfig.LuisServices.TryGetValue("Reservation", out var luisService); + localeConfig.LuisServices.TryGetValue("Restaurant", out var luisService); if (luisService == null) { diff --git a/skills/src/csharp/experimental/restaurantbooking/Dialogs/SkillDialogBase.cs b/skills/src/csharp/experimental/restaurantbooking/Dialogs/SkillDialogBase.cs index 98efb82fde..944b4cb45a 100644 --- a/skills/src/csharp/experimental/restaurantbooking/Dialogs/SkillDialogBase.cs +++ b/skills/src/csharp/experimental/restaurantbooking/Dialogs/SkillDialogBase.cs @@ -73,7 +73,7 @@ protected async Task GetLuisResult(DialogContext dc) // Get luis service for current locale var locale = CultureInfo.CurrentUICulture.TwoLetterISOLanguageName; var localeConfig = Services.CognitiveModelSets[locale]; - var luisService = localeConfig.LuisServices["Reservation"]; + var luisService = localeConfig.LuisServices["Restaurant"]; // Get intent and entities for activity var result = await luisService.RecognizeAsync(dc.Context, CancellationToken.None); From db838c8ce237dc1d964ac11d01610b18a2b89b5d Mon Sep 17 00:00:00 2001 From: litofish Date: Fri, 16 Aug 2019 16:02:46 -0700 Subject: [PATCH 12/23] weather skill .lu file update --- .../Deployment/Resources/LU/en/WeatherSkill.lu | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/skills/src/csharp/experimental/weatherskill/Deployment/Resources/LU/en/WeatherSkill.lu b/skills/src/csharp/experimental/weatherskill/Deployment/Resources/LU/en/WeatherSkill.lu index bc2bbeb2f0..1b3708452a 100644 --- a/skills/src/csharp/experimental/weatherskill/Deployment/Resources/LU/en/WeatherSkill.lu +++ b/skills/src/csharp/experimental/weatherskill/Deployment/Resources/LU/en/WeatherSkill.lu @@ -23,6 +23,17 @@ - will it be raining in ranchi - will it rain this weekend - will it snow today +- whats the weather +- whats the forecast today +- what is the weather this week +- get me the weather forecast for today +- weather today +- what's today's weather +- what is this week's weather report +- find me the weather in bellevue +- get weather forecast for today +- show me the weather +- whats the weather today > # Entity definitions From fc54b8b767d0efe15cb84cf3ccf184f70151023c Mon Sep 17 00:00:00 2001 From: litofish Date: Fri, 16 Aug 2019 16:50:05 -0700 Subject: [PATCH 13/23] Update skill manifests --- .../newsskill/manifestTemplate.json | 55 ++++++++++++++++++- .../weatherskill/manifestTemplate.json | 8 +-- 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/skills/src/csharp/experimental/newsskill/manifestTemplate.json b/skills/src/csharp/experimental/newsskill/manifestTemplate.json index 19a1a5ba1c..b4a86fb88f 100644 --- a/skills/src/csharp/experimental/newsskill/manifestTemplate.json +++ b/skills/src/csharp/experimental/newsskill/manifestTemplate.json @@ -8,8 +8,8 @@ { "id": "newsSkill_findArticles", "definition": { - "description": "Find News articles", - "slots": [ ], + "description": "Find News articles.", + "slots": [], "triggers": { "utteranceSources": [ { @@ -21,6 +21,57 @@ ] } } + }, + { + "id": "newsSkill_trendingArticles", + "definition": { + "description": "Show articles currently trending on social media.", + "slots": [], + "triggers": { + "utteranceSources": [ + { + "locale": "en", + "source": [ + "news#TrendingArticles" + ] + } + ] + } + } + }, + { + "id": "newsSkill_setFavoriteTopics", + "definition": { + "description": "Set the user's favorite news topic.", + "slots": [], + "triggers": { + "utteranceSources": [ + { + "locale": "en", + "source": [ + "news#SetFavoriteTopics" + ] + } + ] + } + } + }, + { + "id": "newsSkill_showFavoriteTopics", + "definition": { + "description": "Show news articles of the user's currently set favorite topic.", + "slots": [], + "triggers": { + "utteranceSources": [ + { + "locale": "en", + "source": [ + "news#ShowFavoriteTopics" + ] + } + ] + } + } } ] } \ No newline at end of file diff --git a/skills/src/csharp/experimental/weatherskill/manifestTemplate.json b/skills/src/csharp/experimental/weatherskill/manifestTemplate.json index d55d6ed13f..5ef406e0ca 100644 --- a/skills/src/csharp/experimental/weatherskill/manifestTemplate.json +++ b/skills/src/csharp/experimental/weatherskill/manifestTemplate.json @@ -1,21 +1,21 @@ { "id": "WeatherSkill", "name": "WeatherSkill", - "description": "", + "description": "The weather skill provides an example of displaying the current weather using AccuWeather.", "iconUrl": "", "authenticationConnections": [], "actions": [ { - "id": "WeatherSkill_Sample", + "id": "WeatherSkill_getForecast", "definition": { - "description": "Sample Skill action with no slots", + "description": "Showing the weather forecast.", "slots": [], "triggers": { "utteranceSources": [ { "locale": "en", "source": [ - "WeatherSkill#Sample" + "WeatherSkill#GetForecast" ] } ] From 90e7dbf053c447edb2b545f07ac2e1cb64c04c9f Mon Sep 17 00:00:00 2001 From: litofish Date: Fri, 16 Aug 2019 17:20:03 -0700 Subject: [PATCH 14/23] skills updates --- .../Services/DispatchLuis.cs | 15 +++-- solutions/HospitalitySample/skills.json | 61 +++++++++++++++++-- 2 files changed, 63 insertions(+), 13 deletions(-) diff --git a/solutions/HospitalitySample/Services/DispatchLuis.cs b/solutions/HospitalitySample/Services/DispatchLuis.cs index 45f853e332..12f69a647b 100644 --- a/solutions/HospitalitySample/Services/DispatchLuis.cs +++ b/solutions/HospitalitySample/Services/DispatchLuis.cs @@ -1,6 +1,5 @@ // -// Code generated by LUISGen C:\Users\t-luitof\source\repos\botframework-solutions\solutions\HospitalitySample\Deployment\Resources\Dispatch\en\LunaHospitalityVAen_Dispatch.json -cs Luis.DispatchLuis -o C:\Users\t-luitof\source\repos\botframework-solutions\solutions\HospitalitySample\Services -// Tool github: https://github.com/microsoft/botbuilder-tools +// Code generated by LUISGen // Changes may cause incorrect behavior and will be lost if the code is // regenerated. // @@ -20,10 +19,10 @@ public enum Intent { q_Faq, q_hotel_FAQ, restaurantBookingSkill, - newsSkill, pointOfInterestSkill, - WeatherSkill, hospitalitySkill, + WeatherSkill, + newsSkill, None }; public Dictionary Intents; @@ -31,20 +30,20 @@ public enum Intent { public class _Entities { // Simple entities - public string[] topic; - public string[] site; public string[] KEYWORD; public string[] ADDRESS; public string[] Item; + public string[] topic; + public string[] site; // Instance public class _Instance { - public InstanceData[] topic; - public InstanceData[] site; public InstanceData[] KEYWORD; public InstanceData[] ADDRESS; public InstanceData[] Item; + public InstanceData[] topic; + public InstanceData[] site; } [JsonProperty("$instance")] public _Instance _instance; diff --git a/solutions/HospitalitySample/skills.json b/solutions/HospitalitySample/skills.json index b78e5aa3c6..ea80988b04 100644 --- a/solutions/HospitalitySample/skills.json +++ b/solutions/HospitalitySample/skills.json @@ -165,7 +165,7 @@ { "id": "newsSkill_findArticles", "definition": { - "description": "Find News articles", + "description": "Find News articles.", "slots": [], "triggers": { "utteranceSources": [ @@ -178,6 +178,57 @@ ] } } + }, + { + "id": "newsSkill_trendingArticles", + "definition": { + "description": "Show articles currently trending on social media.", + "slots": [], + "triggers": { + "utteranceSources": [ + { + "locale": "en", + "source": [ + "news#TrendingArticles" + ] + } + ] + } + } + }, + { + "id": "newsSkill_setFavoriteTopics", + "definition": { + "description": "Set the user's favorite news topic.", + "slots": [], + "triggers": { + "utteranceSources": [ + { + "locale": "en", + "source": [ + "news#SetFavoriteTopics" + ] + } + ] + } + } + }, + { + "id": "newsSkill_showFavoriteTopics", + "definition": { + "description": "Show news articles of the user's currently set favorite topic.", + "slots": [], + "triggers": { + "utteranceSources": [ + { + "locale": "en", + "source": [ + "news#ShowFavoriteTopics" + ] + } + ] + } + } } ] }, @@ -293,20 +344,20 @@ "msaAppId": "", "name": "WeatherSkill", "endpoint": "", - "description": "", + "description": "The weather skill provides an example of displaying the current weather using AccuWeather.", "authenticationConnections": [], "actions": [ { - "id": "WeatherSkill_Sample", + "id": "WeatherSkill_getForecast", "definition": { - "description": "Sample Skill action with no slots", + "description": "Showing the weather forecast.", "slots": [], "triggers": { "utteranceSources": [ { "locale": "en", "source": [ - "WeatherSkill#Sample" + "WeatherSkill#GetForecast" ] } ] From 579d77484446332746051efe8f2ec443b4a240a3 Mon Sep 17 00:00:00 2001 From: litofish Date: Mon, 19 Aug 2019 14:16:26 -0700 Subject: [PATCH 15/23] Add files for event skill from skill template --- skills/src/csharp/Skills.sln | 15 +- .../experimental/eventskill/.filenesting.json | 15 + .../eventskill/Adapters/DefaultAdapter.cs | 43 ++ .../eventskill/Adapters/EventSkillAdapter.cs | 43 ++ .../experimental/eventskill/Bots/DialogBot.cs | 46 ++ .../ConnectedService.json | 7 + .../eventskill/Controllers/BotController.cs | 24 + .../Deployment/Resources/LU/en/Event.lu | 13 + .../Deployment/Resources/LU/en/General.lu | 521 ++++++++++++++++++ .../Resources/parameters.template.json | 24 + .../Deployment/Resources/template.json | 245 ++++++++ .../eventskill/Deployment/Scripts/deploy.ps1 | 245 ++++++++ .../Scripts/deploy_cognitive_models.ps1 | 300 ++++++++++ .../Deployment/Scripts/luis_functions.ps1 | 112 ++++ .../eventskill/Deployment/Scripts/publish.ps1 | 66 +++ .../Deployment/Scripts/qna_functions.ps1 | 112 ++++ .../Scripts/update_cognitive_models.ps1 | 159 ++++++ .../eventskill/Dialogs/MainDialog.cs | 246 +++++++++ .../eventskill/Dialogs/SkillDialogBase.cs | 175 ++++++ .../experimental/eventskill/EventSkill.csproj | 166 ++++++ .../eventskill/Models/EventSkillState.cs | 18 + .../eventskill/Pipeline/EventSkill.yml | 61 ++ .../csharp/experimental/eventskill/Program.cs | 21 + .../eventskill/Properties/launchSettings.json | 27 + .../Responses/Main/MainResponses.cs | 23 + .../Responses/Main/MainResponses.de.json | Bin 0 -> 4004 bytes .../Responses/Main/MainResponses.es.json | Bin 0 -> 3860 bytes .../Responses/Main/MainResponses.fr.json | Bin 0 -> 4040 bytes .../Responses/Main/MainResponses.it.json | Bin 0 -> 3948 bytes .../Responses/Main/MainResponses.json | 83 +++ .../Responses/Main/MainResponses.tt | 3 + .../Responses/Main/MainResponses.zh.json | 82 +++ .../Responses/Shared/ResponseIdCollection.t4 | 31 ++ .../Responses/Shared/SharedResponses.cs | 22 + .../Responses/Shared/SharedResponses.de.json | Bin 0 -> 11500 bytes .../Responses/Shared/SharedResponses.es.json | Bin 0 -> 10194 bytes .../Responses/Shared/SharedResponses.fr.json | Bin 0 -> 11096 bytes .../Responses/Shared/SharedResponses.it.json | Bin 0 -> 10412 bytes .../Responses/Shared/SharedResponses.json | 148 +++++ .../Responses/Shared/SharedResponses.tt | 3 + .../Responses/Shared/SharedResponses.zh.json | 100 ++++ .../eventskill/Services/BotServices.cs | 69 +++ .../eventskill/Services/BotSettings.cs | 11 + .../eventskill/Services/EventLuis.cs | 64 +++ .../eventskill/Services/GeneralLuis.cs | 89 +++ .../csharp/experimental/eventskill/Startup.cs | 127 +++++ .../experimental/eventskill/appsettings.json | 19 + .../eventskill/cognitivemodels.json | 29 + .../eventskill/manifestTemplate.json | 26 + .../csharp/experimental/eventskill/readme.md | 1 + .../eventskill/wwwroot/default.htm | 426 ++++++++++++++ 51 files changed, 4058 insertions(+), 2 deletions(-) create mode 100644 skills/src/csharp/experimental/eventskill/.filenesting.json create mode 100644 skills/src/csharp/experimental/eventskill/Adapters/DefaultAdapter.cs create mode 100644 skills/src/csharp/experimental/eventskill/Adapters/EventSkillAdapter.cs create mode 100644 skills/src/csharp/experimental/eventskill/Bots/DialogBot.cs create mode 100644 skills/src/csharp/experimental/eventskill/Connected Services/Application Insights/ConnectedService.json create mode 100644 skills/src/csharp/experimental/eventskill/Controllers/BotController.cs create mode 100644 skills/src/csharp/experimental/eventskill/Deployment/Resources/LU/en/Event.lu create mode 100644 skills/src/csharp/experimental/eventskill/Deployment/Resources/LU/en/General.lu create mode 100644 skills/src/csharp/experimental/eventskill/Deployment/Resources/parameters.template.json create mode 100644 skills/src/csharp/experimental/eventskill/Deployment/Resources/template.json create mode 100644 skills/src/csharp/experimental/eventskill/Deployment/Scripts/deploy.ps1 create mode 100644 skills/src/csharp/experimental/eventskill/Deployment/Scripts/deploy_cognitive_models.ps1 create mode 100644 skills/src/csharp/experimental/eventskill/Deployment/Scripts/luis_functions.ps1 create mode 100644 skills/src/csharp/experimental/eventskill/Deployment/Scripts/publish.ps1 create mode 100644 skills/src/csharp/experimental/eventskill/Deployment/Scripts/qna_functions.ps1 create mode 100644 skills/src/csharp/experimental/eventskill/Deployment/Scripts/update_cognitive_models.ps1 create mode 100644 skills/src/csharp/experimental/eventskill/Dialogs/MainDialog.cs create mode 100644 skills/src/csharp/experimental/eventskill/Dialogs/SkillDialogBase.cs create mode 100644 skills/src/csharp/experimental/eventskill/EventSkill.csproj create mode 100644 skills/src/csharp/experimental/eventskill/Models/EventSkillState.cs create mode 100644 skills/src/csharp/experimental/eventskill/Pipeline/EventSkill.yml create mode 100644 skills/src/csharp/experimental/eventskill/Program.cs create mode 100644 skills/src/csharp/experimental/eventskill/Properties/launchSettings.json create mode 100644 skills/src/csharp/experimental/eventskill/Responses/Main/MainResponses.cs create mode 100644 skills/src/csharp/experimental/eventskill/Responses/Main/MainResponses.de.json create mode 100644 skills/src/csharp/experimental/eventskill/Responses/Main/MainResponses.es.json create mode 100644 skills/src/csharp/experimental/eventskill/Responses/Main/MainResponses.fr.json create mode 100644 skills/src/csharp/experimental/eventskill/Responses/Main/MainResponses.it.json create mode 100644 skills/src/csharp/experimental/eventskill/Responses/Main/MainResponses.json create mode 100644 skills/src/csharp/experimental/eventskill/Responses/Main/MainResponses.tt create mode 100644 skills/src/csharp/experimental/eventskill/Responses/Main/MainResponses.zh.json create mode 100644 skills/src/csharp/experimental/eventskill/Responses/Shared/ResponseIdCollection.t4 create mode 100644 skills/src/csharp/experimental/eventskill/Responses/Shared/SharedResponses.cs create mode 100644 skills/src/csharp/experimental/eventskill/Responses/Shared/SharedResponses.de.json create mode 100644 skills/src/csharp/experimental/eventskill/Responses/Shared/SharedResponses.es.json create mode 100644 skills/src/csharp/experimental/eventskill/Responses/Shared/SharedResponses.fr.json create mode 100644 skills/src/csharp/experimental/eventskill/Responses/Shared/SharedResponses.it.json create mode 100644 skills/src/csharp/experimental/eventskill/Responses/Shared/SharedResponses.json create mode 100644 skills/src/csharp/experimental/eventskill/Responses/Shared/SharedResponses.tt create mode 100644 skills/src/csharp/experimental/eventskill/Responses/Shared/SharedResponses.zh.json create mode 100644 skills/src/csharp/experimental/eventskill/Services/BotServices.cs create mode 100644 skills/src/csharp/experimental/eventskill/Services/BotSettings.cs create mode 100644 skills/src/csharp/experimental/eventskill/Services/EventLuis.cs create mode 100644 skills/src/csharp/experimental/eventskill/Services/GeneralLuis.cs create mode 100644 skills/src/csharp/experimental/eventskill/Startup.cs create mode 100644 skills/src/csharp/experimental/eventskill/appsettings.json create mode 100644 skills/src/csharp/experimental/eventskill/cognitivemodels.json create mode 100644 skills/src/csharp/experimental/eventskill/manifestTemplate.json create mode 100644 skills/src/csharp/experimental/eventskill/readme.md create mode 100644 skills/src/csharp/experimental/eventskill/wwwroot/default.htm diff --git a/skills/src/csharp/Skills.sln b/skills/src/csharp/Skills.sln index 75a2a8cef7..2bd17dddba 100644 --- a/skills/src/csharp/Skills.sln +++ b/skills/src/csharp/Skills.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29102.190 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.779 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Skills", "Skills", "{34280F2E-60EB-4566-9ECD-56F114372037}" EndProject @@ -41,6 +41,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HospitalitySkill", "experim EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MusicSkill", "experimental\musicskill\MusicSkill.csproj", "{A2ECB4BF-FD59-4746-B699-F1C326D561BB}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventSkill", "experimental\eventskill\EventSkill.csproj", "{5BF2293A-6E56-464A-8355-EDC8972F7E09}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug - NuGet Packages|Any CPU = Debug - NuGet Packages|Any CPU @@ -177,6 +179,14 @@ Global {A2ECB4BF-FD59-4746-B699-F1C326D561BB}.Documentation|Any CPU.Build.0 = Debug|Any CPU {A2ECB4BF-FD59-4746-B699-F1C326D561BB}.Release|Any CPU.ActiveCfg = Release|Any CPU {A2ECB4BF-FD59-4746-B699-F1C326D561BB}.Release|Any CPU.Build.0 = Release|Any CPU + {5BF2293A-6E56-464A-8355-EDC8972F7E09}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug|Any CPU + {5BF2293A-6E56-464A-8355-EDC8972F7E09}.Debug - NuGet Packages|Any CPU.Build.0 = Debug|Any CPU + {5BF2293A-6E56-464A-8355-EDC8972F7E09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5BF2293A-6E56-464A-8355-EDC8972F7E09}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5BF2293A-6E56-464A-8355-EDC8972F7E09}.Documentation|Any CPU.ActiveCfg = Debug|Any CPU + {5BF2293A-6E56-464A-8355-EDC8972F7E09}.Documentation|Any CPU.Build.0 = Debug|Any CPU + {5BF2293A-6E56-464A-8355-EDC8972F7E09}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5BF2293A-6E56-464A-8355-EDC8972F7E09}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -198,6 +208,7 @@ Global {8CB101FA-B496-4507-80A2-151FAB7E8ED1} = {3665D242-1A88-4860-B148-BAB695B7B5E4} {0EFEA4F2-DC7E-436E-B951-E9B566AFF7F0} = {3665D242-1A88-4860-B148-BAB695B7B5E4} {A2ECB4BF-FD59-4746-B699-F1C326D561BB} = {3665D242-1A88-4860-B148-BAB695B7B5E4} + {5BF2293A-6E56-464A-8355-EDC8972F7E09} = {3665D242-1A88-4860-B148-BAB695B7B5E4} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {7B849B7E-CCF5-4031-91F7-CA835433B457} diff --git a/skills/src/csharp/experimental/eventskill/.filenesting.json b/skills/src/csharp/experimental/eventskill/.filenesting.json new file mode 100644 index 0000000000..90c31b9e8a --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/.filenesting.json @@ -0,0 +1,15 @@ +{ + "help": "https://go.microsoft.com/fwlink/?linkid=866610", + "dependentFileProviders": { + "add": { + "pathSegment": { + "add": { + ".*": [ + ".json", + ".resx" + ] + } + } + } + } +} \ No newline at end of file diff --git a/skills/src/csharp/experimental/eventskill/Adapters/DefaultAdapter.cs b/skills/src/csharp/experimental/eventskill/Adapters/DefaultAdapter.cs new file mode 100644 index 0000000000..d46b799671 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Adapters/DefaultAdapter.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Globalization; +using EventSkill.Responses.Shared; +using EventSkill.Services; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Azure; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Builder.Solutions.Middleware; +using Microsoft.Bot.Builder.Solutions.Responses; +using Microsoft.Bot.Connector.Authentication; +using Microsoft.Bot.Schema; + +namespace EventSkill.Bots +{ + public class DefaultAdapter : BotFrameworkHttpAdapter + { + public DefaultAdapter( + BotSettings settings, + ICredentialProvider credentialProvider, + IBotTelemetryClient telemetryClient, + ResponseManager responseManager) + : base(credentialProvider) + { + OnTurnError = async (context, exception) => + { + CultureInfo.CurrentUICulture = new CultureInfo(context.Activity.Locale); + await context.SendActivityAsync(responseManager.GetResponse(SharedResponses.ErrorMessage)); + await context.SendActivityAsync(new Activity(type: ActivityTypes.Trace, text: $"Skill Error: {exception.Message} | {exception.StackTrace}")); + telemetryClient.TrackException(exception); + }; + + // Uncomment the following line for local development without Azure Storage + // Use(new TranscriptLoggerMiddleware(new MemoryTranscriptStore())); + Use(new TranscriptLoggerMiddleware(new AzureBlobTranscriptStore(settings.BlobStorage.ConnectionString, settings.BlobStorage.Container))); + Use(new TelemetryLoggerMiddleware(telemetryClient, logPersonalInformation: true)); + Use(new ShowTypingMiddleware()); + Use(new SetLocaleMiddleware(settings.DefaultLocale ?? "en-us")); + Use(new EventDebuggerMiddleware()); + } + } +} \ No newline at end of file diff --git a/skills/src/csharp/experimental/eventskill/Adapters/EventSkillAdapter.cs b/skills/src/csharp/experimental/eventskill/Adapters/EventSkillAdapter.cs new file mode 100644 index 0000000000..dbd040ca88 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Adapters/EventSkillAdapter.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Globalization; +using EventSkill.Responses.Shared; +using EventSkill.Services; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Azure; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Builder.Skills; +using Microsoft.Bot.Builder.Solutions.Middleware; +using Microsoft.Bot.Builder.Solutions.Responses; +using Microsoft.Bot.Schema; + +namespace EventSkill.Adapters +{ + public class EventSkillAdapter : SkillWebSocketBotAdapter + { + public EventSkillAdapter( + BotSettings settings, + UserState userState, + ConversationState conversationState, + ResponseManager responseManager, + IBotTelemetryClient telemetryClient) + { + OnTurnError = async (context, exception) => + { + CultureInfo.CurrentUICulture = new CultureInfo(context.Activity.Locale); + await context.SendActivityAsync(responseManager.GetResponse(SharedResponses.ErrorMessage)); + await context.SendActivityAsync(new Activity(type: ActivityTypes.Trace, text: $"Skill Error: {exception.Message} | {exception.StackTrace}")); + telemetryClient.TrackException(exception); + }; + + // Uncomment the following line for local development without Azure Storage + // Use(new TranscriptLoggerMiddleware(new MemoryTranscriptStore())); + Use(new TranscriptLoggerMiddleware(new AzureBlobTranscriptStore(settings.BlobStorage.ConnectionString, settings.BlobStorage.Container))); + Use(new TelemetryLoggerMiddleware(telemetryClient, logPersonalInformation: true)); + Use(new SetLocaleMiddleware(settings.DefaultLocale ?? "en-us")); + Use(new EventDebuggerMiddleware()); + Use(new SkillMiddleware(userState, conversationState, conversationState.CreateProperty(nameof(EventSkill)))); + } + } +} diff --git a/skills/src/csharp/experimental/eventskill/Bots/DialogBot.cs b/skills/src/csharp/experimental/eventskill/Bots/DialogBot.cs new file mode 100644 index 0000000000..5a1f9c9f38 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Bots/DialogBot.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Schema; +using Microsoft.Extensions.DependencyInjection; + +namespace EventSkill.Bots +{ + public class DialogBot : IBot + where T : Dialog + { + private readonly Dialog _dialog; + private readonly BotState _conversationState; + private readonly BotState _userState; + private readonly IBotTelemetryClient _telemetryClient; + + public DialogBot(IServiceProvider serviceProvider, T dialog) + { + _dialog = dialog; + _conversationState = serviceProvider.GetService(); + _userState = serviceProvider.GetService(); + _telemetryClient = serviceProvider.GetService(); + } + + public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken)) + { + // Client notifying this bot took to long to respond (timed out) + if (turnContext.Activity.Code == EndOfConversationCodes.BotTimedOut) + { + _telemetryClient.TrackTrace($"Timeout in {turnContext.Activity.ChannelId} channel: Bot took too long to respond.", Severity.Information, null); + return; + } + + await _dialog.RunAsync(turnContext, _conversationState.CreateProperty(nameof(DialogState)), cancellationToken); + + // Save any state changes that might have occured during the turn. + await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken); + await _userState.SaveChangesAsync(turnContext, false, cancellationToken); + } + } +} \ No newline at end of file diff --git a/skills/src/csharp/experimental/eventskill/Connected Services/Application Insights/ConnectedService.json b/skills/src/csharp/experimental/eventskill/Connected Services/Application Insights/ConnectedService.json new file mode 100644 index 0000000000..d15936a838 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Connected Services/Application Insights/ConnectedService.json @@ -0,0 +1,7 @@ +{ + "ProviderId": "Microsoft.ApplicationInsights.ConnectedService.ConnectedServiceProvider", + "Version": "8.13.10627.1", + "GettingStartedDocument": { + "Uri": "https://go.microsoft.com/fwlink/?LinkID=798432" + } +} \ No newline at end of file diff --git a/skills/src/csharp/experimental/eventskill/Controllers/BotController.cs b/skills/src/csharp/experimental/eventskill/Controllers/BotController.cs new file mode 100644 index 0000000000..a4ffdf4398 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Controllers/BotController.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.AspNetCore.Mvc; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Builder.Skills; +using Microsoft.Bot.Builder.Solutions; + +namespace EventSkill.Controllers +{ + [ApiController] + public class BotController : SkillController + { + public BotController( + IBot bot, + BotSettingsBase botSettings, + IBotFrameworkHttpAdapter botFrameworkHttpAdapter, + SkillWebSocketAdapter skillWebSocketAdapter) + : base(bot, botSettings, botFrameworkHttpAdapter, skillWebSocketAdapter) + { + } + } +} \ No newline at end of file diff --git a/skills/src/csharp/experimental/eventskill/Deployment/Resources/LU/en/Event.lu b/skills/src/csharp/experimental/eventskill/Deployment/Resources/LU/en/Event.lu new file mode 100644 index 0000000000..3eb952794b --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Deployment/Resources/LU/en/Event.lu @@ -0,0 +1,13 @@ +# GetEvents +- find events near me +- get local events +- what's happening nearby +- are there any events near me +- find me something to do +- can you recommend events in the area + +# None +- hi +- hello +- logout +- goodbye \ No newline at end of file diff --git a/skills/src/csharp/experimental/eventskill/Deployment/Resources/LU/en/General.lu b/skills/src/csharp/experimental/eventskill/Deployment/Resources/LU/en/General.lu new file mode 100644 index 0000000000..7ede276df9 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Deployment/Resources/LU/en/General.lu @@ -0,0 +1,521 @@ +> # Intent definitions + +## Cancel +- cancel +- cancel app +- cancel cancel +- cancel it +- cancel never mind +- cancel that +- canceled +- cancelled +- do nothing +- don't do that +- don't do that anymore +- forget about it +- go away +- just cancel +- just cancel it +- nerver mind +- never mind +- never mind cancel +- never mind cancel that +- no cancel +- no cancel cancel +- no cancel it +- no cancel that +- no never mind +- no no cancel +- no no cancel it +- nothing never mind +- nothing please +- oh cancel +- oh don't do that +- please do nothing +- quit +- sorry, don't do it + + +## Confirm +- confirm +- do it +- fine +- go for it +- great +- i'm sure +- it's fine +- no doubt +- of course +- oh yeah +- oh yes +- ok +- ok for now +- okay +- perfect +- perfect thank you +- right +- right yes +- sounds good +- sounds good thank you +- sounds good thanks +- sounds good to me +- sounds great +- sounds perfect +- sure +- sure does +- sure is +- sure thing +- sure yes +- thank you very much +- thank you yes +- that is correct +- that is right +- that right +- that sounds good +- that's fine +- this is good +- very good +- ya +- yeah +- yeah baby +- yeah bro +- yeah buddy +- yeah cool +- yeah go ahead +- yeah go for it +- yeah good +- yes + + +## Escalate +- can i talk to a person +- contact support +- contact the customer service +- customer service +- human service +- i need manual customer service +- i need real human help +- i need support +- i want to talk to a human +- i want to talk to a real human +- is there any person i can talk to +- is there any real human +- is there any real person +- talk to a human + + +## FinishTask +- all finished +- all set +- done +- finish +- finished +- finished with +- i am done +- i am finished +- it finished +- it's done +- it's finished +- just be +- submit +- submit it + + +## GoBack +- back +- back please +- back to +- back to last +- back to last step +- get back +- get back please +- get back to last +- go back +- go back on +- go back please +- go back to +- last step +- no go back to +- no no go back to +- please return +- return + + +## Help +- any help +- can you help +- can you help me +- give me some help +- help +- how can i get it +- how to do it +- i need help +- i need some assist +- i need some help +- is there any help +- open help +- please help +- some help +- who can help me + +## Logout +- signout +- forget me +- sign out +- logout +- log out + +## None +- all of them +- i want them all +- i want to all of them + + +## ReadAloud +- can you read it +- can you read that +- can you read that for me +- can you you read page aloud +- could you tell me what that says +- detail aloud what that says +- hey read that for me +- i need to hear this page +- i would like you to read that for me +- make a reading of this page +- please read +- please read it +- please read me the page +- please read my latest email +- please read this +- please read this out loud +- please read this page +- please read this page aloud +- please read this page out loud +- please read this to me +- read all on the screen to me +- read aloud +- read aloud the current text onscreen +- read file aloud +- read it +- read it aloud +- read it out loud +- read it outloud +- read it please +- read it to me +- read me this page +- read my to list +- read outloud +- read page +- read page aloud +- read page outloud +- read sentence out loud +- read text +- read text aloud +- read that +- read that out loud +- read the page +- read the page onscreen to me +- read the page out loud +- read the page to me +- read the text to me +- read the words on this page +- read this for me please +- read this page +- read this page aloud +- read this page out loud +- read this page to me +- read to me +- read what is currently on the screen to me +- speak of what is on this page +- start reading this +- tell me about the information on the screen +- tell me the current text on screen +- vocalize what s on the page +- what does the page say +- would you please read that for me +- would you read that out loud please +- would you read that to me please + + +## Reject +- i don't like it +- i reject +- negative +- never +- no +- no i don't want that +- no later +- no leave it +- no more no +- no no +- no no no +- no no thank you +- no not that one +- no reject it +- no thank you +- no thanks +- no way +- no wrong +- nope +- not +- not at all +- not even close +- not exactly +- not now +- not quite +- not right now +- not that +- nothing much +- oh no +- reject +- reject it + + +## Repeat +- again +- could you say it again +- i didn't hear repeat again +- i have not heard +- pardon +- repeat +- repeat please +- repeat that +- say again +- say again please +- say that again +- sorry +- what +- what did you say +- what was that again + + +## SelectAny +- any of it +- any one is ok +- anyone is fine +- anything +- choose anyone +- choose one of it randomly +- opt for a random one +- opt for any of it +- select a random choice +- select a random one +- select any +- select any choice +- select any of it + + +## SelectItem +- choose last +- choose last one +- choose no.2 +- choose the {DirectionalReference=bottom left} +- choose the first choice +- choose the fourth one +- choose the {DirectionalReference=upper left} choice +- choose the {DirectionalReference=upper right} one +- choose {DirectionalReference=top right} +- choose {DirectionalReference=top right} one +- i like {DirectionalReference=left} one +- i like second +- i like second one +- i like the {DirectionalReference=bottom} one +- i like the first one +- i like the third +- i like the third choice +- i like the {DirectionalReference=top right} one +- i like the {DirectionalReference=upper right} +- i like {DirectionalReference=upper right} +- i want {DirectionalReference=bottom} +- i want fourth +- i want {DirectionalReference=left} +- i want {DirectionalReference=right} one +- i want the first +- i want the fourth choice +- i want the {DirectionalReference=left} +- i want the {DirectionalReference=lower} choice +- i want the {DirectionalReference=right} one +- i want the second one +- i want third one +- i want to choose {DirectionalReference=bottom} one +- i want to choose {DirectionalReference=lower right} +- i want to choose {DirectionalReference=right} +- i want to choose second one +- i want to choose the first one +- i want to choose the fourth +- i want to choose the last choice +- i want to choose the {DirectionalReference=left} one +- i want to choose the {DirectionalReference=lower} choice +- i want to choose the {DirectionalReference=right} +- i want to choose third +- opt for first one +- opt for last +- opt for {DirectionalReference=left} +- opt for {DirectionalReference=right} one +- opt for the last one +- opt for the {DirectionalReference=left} one +- opt for the {DirectionalReference=lower} choice +- opt for the {DirectionalReference=right} +- opt for the second +- opt for the second choice +- select fourth one +- select {DirectionalReference=lower} one +- select no.5 +- select the {DirectionalReference=bottom} choice +- select the first +- select the last choice +- select the {DirectionalReference=lower} one +- select the {DirectionalReference=right} one +- select the third one +- select the {DirectionalReference=upper right} +- select third +- select {DirectionalReference=upper} +- what about the last +- what about the third one + + +## SelectNone +- i don't want to choose any one +- i don't want to select any one +- i want neither of them +- i want none of them +- neither +- neither of those +- neither one +- neither one of them +- neither thank you +- none +- none none +- none none of them +- none of them +- none of them thank you +- none of these +- none of those +- they look bad, can you give me other choices + + +## ShowNext +- and after that +- display more +- displays more +- give me more +- go forward +- go to the next one +- go to the next three items +- i need to go to the next one +- i want more +- more +- move to the next one +- next +- reveal more +- show me the next +- show more +- show the next 3 +- show the next 4 items +- show the next one +- show the next two options +- tell me more +- tell more +- what about next one +- what after that +- what's after that +- what's more +- what's next +- what's the next 2 +- what's up next + + +## ShowPrevious +- back to the last one +- bring the previous one +- display previously +- get back to the last one +- go back to last one +- go back to previous +- go back to the last one +- go previously +- go to the previous +- go to the previous one +- previous one +- previous one please +- return to the previous one +- reveal previous +- reveal previously +- show earlier +- show me the previous one +- show previous +- show the previous one +- what before that +- what is the previous +- what's before that + + +## StartOver +- clear and start again +- could you start it over +- please begin again +- restart +- restart it +- start again +- start it over +- start over +- start over it +- turn over a new leaf + + +## Stop +- baby just be quiet +- be quiet +- be quiet now +- come on stop +- dismiss +- end +- end it +- exit exit +- exit stop +- god shut up +- hey stop +- i love you to stop talking +- i mean stop listening +- i said stop +- just be quiet +- my god shut up +- never mind stop +- no be quiet +- no be quiet now +- no no no no stop talking +- no shut up +- no stop +- nobody cares stop talking +- nowhere just be quiet +- oh my god shut up +- ok stop stop +- quiet +- quiet now +- shut the fuck up +- shut up +- shut up be quiet +- shut up quiet +- shut your mouth +- stop please +- stop talking +- turn off +- turn off stop + + +> # Entity definitions + +$DirectionalReference:simple + + +> # PREBUILT Entity definitions + +$PREBUILT:number + +$PREBUILT:ordinal + + +> # Phrase list definitions + + +> # List entities diff --git a/skills/src/csharp/experimental/eventskill/Deployment/Resources/parameters.template.json b/skills/src/csharp/experimental/eventskill/Deployment/Resources/parameters.template.json new file mode 100644 index 0000000000..95841eb589 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Deployment/Resources/parameters.template.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "useCosmosDb": { + "value": false + }, + "useStorage": { + "value": false + }, + "appServicePlanSku": { + "value": { + "tier": "Free", + "name": "F1" + } + }, + "botServiceSku": { + "value": "F0" + }, + "luisServiceSku": { + "value": "F0" + } + } +} \ No newline at end of file diff --git a/skills/src/csharp/experimental/eventskill/Deployment/Resources/template.json b/skills/src/csharp/experimental/eventskill/Deployment/Resources/template.json new file mode 100644 index 0000000000..5eb8a2cab3 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Deployment/Resources/template.json @@ -0,0 +1,245 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json", + "contentVersion": "1.0.0.0", + "parameters": { + "name": { + "type": "string", + "defaultValue": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "suffix": { + "type": "string", + "defaultValue": "[take(uniqueString(resourceGroup().id), 7)]" + }, + "microsoftAppId": { + "type": "string" + }, + "microsoftAppPassword": { + "type": "string" + }, + "useCosmosDb": { + "type": "bool", + "defaultValue": true + }, + "cosmosDbName": { + "type": "string", + "defaultValue": "[concat(parameters('name'), '-', parameters('suffix'))]" + }, + "useStorage": { + "type": "bool", + "defaultValue": true + }, + "storageAccountName": { + "type": "string", + "defaultValue": "[concat(parameters('name'), '-', parameters('suffix'))]" + }, + "appServicePlanName": { + "type": "string", + "defaultValue": "[concat(parameters('name'), '-', parameters('suffix'))]" + }, + "appServicePlanSku": { + "type": "object", + "defaultValue": { + "tier": "Standard", + "name": "S1" + } + }, + "appInsightsName": { + "type": "string", + "defaultValue": "[concat(parameters('name'), '-', parameters('suffix'))]" + }, + "appInsightsLocation": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "botWebAppName": { + "type": "string", + "defaultValue": "[concat(parameters('name'), '-', parameters('suffix'))]" + }, + "botServiceName": { + "type": "string", + "defaultValue": "[concat(parameters('name'), '-', parameters('suffix'))]" + }, + "botServiceSku": { + "type": "string", + "defaultValue": "S1" + }, + "luisServiceName": { + "type": "string", + "defaultValue": "[concat(parameters('name'), '-luis-', parameters('suffix'))]" + }, + "luisServiceSku": { + "type": "string", + "defaultValue": "S0" + }, + "luisServiceLocation": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + } + }, + "variables": { + "botWebAppName": "[replace(parameters('botWebAppName'), '_', '')]", + "storageAccountName": "[toLower(take(replace(replace(parameters('storageAccountName'), '-', ''), '_', ''), 24))]", + "cosmosDbAccountName": "[toLower(take(replace(parameters('cosmosDbName'), '_', ''), 31))]", + "botEndpoint": "[concat('https://', toLower(variables('botWebAppName')), '.azurewebsites.net/api/messages')]" + }, + "resources": [ + { + "apiVersion": "2018-02-01", + "name": "99ea37e6-a3e6-4102-a249-71c880607386", + "type": "Microsoft.Resources/deployments", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [] + } + } + }, + { + "comments": "CosmosDB for bot state.", + "type": "Microsoft.DocumentDB/databaseAccounts", + "kind": "GlobalDocumentDB", + "apiVersion": "2015-04-08", + "name": "[variables('cosmosDbAccountName')]", + "location": "[parameters('location')]", + "properties": { + "databaseAccountOfferType": "Standard", + "locations": [ + { + "locationName": "[parameters('location')]", + "failoverPriority": 0 + } + ] + }, + "condition": "[parameters('useCosmosDb')]" + }, + { + "comments": "storage account", + "type": "Microsoft.Storage/storageAccounts", + "kind": "StorageV2", + "apiVersion": "2018-07-01", + "name": "[variables('storageAccountName')]", + "location": "[parameters('location')]", + "sku": { + "name": "Standard_LRS" + }, + "condition": "[parameters('useStorage')]" + }, + { + "comments": "app service plan", + "type": "Microsoft.Web/serverFarms", + "apiVersion": "2018-02-01", + "name": "[parameters('appServicePlanName')]", + "location": "[parameters('location')]", + "sku": "[parameters('appServicePlanSku')]", + "properties": {} + }, + { + "comments": "app insights", + "type": "Microsoft.Insights/components", + "kind": "web", + "apiVersion": "2015-05-01", + "name": "[parameters('appInsightsName')]", + "location": "[parameters('appInsightsLocation')]", + "properties": { + "Application_Type": "web" + } + }, + { + "comments": "bot web app", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-02-01", + "name": "[variables('botWebAppName')]", + "location": "[parameters('location')]", + "properties": { + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('appServicePlanName'))]", + "siteConfig": { + "webSocketsEnabled": true, + "appSettings": [ + { + "name": "MicrosoftAppId", + "value": "[parameters('microsoftAppId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('microsoftAppPassword')]" + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', parameters('appServicePlanName'))]" + ] + }, + { + "comments": "bot service", + "type": "Microsoft.BotService/botServices", + "kind": "sdk", + "apiVersion": "2018-07-12", + "name": "[parameters('botServiceName')]", + "location": "global", + "sku": { + "name": "[parameters('botServiceSku')]" + }, + "properties": { + "displayName": "[parameters('botServiceName')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('microsoftAppId')]", + "developerAppInsightKey": "[reference(resourceId('Microsoft.Insights/components', parameters('appInsightsName'))).InstrumentationKey]", + "developerAppInsightsApplicationId": "[reference(resourceId('Microsoft.Insights/components', parameters('appInsightsName'))).ApplicationId]" + } + }, + { + "comments": "Cognitive service key for all LUIS apps.", + "type": "Microsoft.CognitiveServices/accounts", + "kind": "LUIS", + "apiVersion": "2017-04-18", + "name": "[parameters('luisServiceName')]", + "location": "[parameters('luisServiceLocation')]", + "sku": { + "name": "[parameters('luisServiceSku')]" + } + } + ], + "outputs": { + "botWebAppName": { + "type": "string", + "value": "[variables('botWebAppName')]" + }, + "ApplicationInsights": { + "type": "object", + "value": { + "InstrumentationKey": "[reference(resourceId('Microsoft.Insights/components', parameters('appInsightsName'))).InstrumentationKey]" + } + }, + "blobStorage": { + "type": "object", + "value": { + "connectionString": "[if(parameters('useStorage'), concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2018-07-01').keys[0].value, ';EndpointSuffix=core.windows.net'), '')]", + "container": "transcripts" + } + }, + "cosmosDb": { + "type": "object", + "value": { + "cosmosDBEndpoint": "[if(parameters('useCosmosDb'), reference(resourceId('Microsoft.DocumentDB/databaseAccounts', variables('cosmosDbAccountName'))).documentEndpoint, '')]", + "authKey": "[if(parameters('useCosmosDb'), listKeys(resourceId('Microsoft.DocumentDB/databaseAccounts', variables('cosmosDbAccountName')), '2015-04-08').primaryMasterKey, '')]", + "databaseId": "botstate-db", + "collectionId": "botstate-collection" + } + }, + "luis": { + "type": "object", + "value": { + "accountName": "[parameters('luisServiceName')]", + "region": "[parameters('luisServiceLocation')]", + "key": "[listKeys(resourceId('Microsoft.CognitiveServices/accounts', parameters('luisServiceName')),'2017-04-18').key1]" + } + } + } +} \ No newline at end of file diff --git a/skills/src/csharp/experimental/eventskill/Deployment/Scripts/deploy.ps1 b/skills/src/csharp/experimental/eventskill/Deployment/Scripts/deploy.ps1 new file mode 100644 index 0000000000..2ec874cf98 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Deployment/Scripts/deploy.ps1 @@ -0,0 +1,245 @@ +#Requires -Version 6 + +Param( + [string] $name, + [string] $resourceGroup, + [string] $location, + [string] $appId, + [string] $appPassword, + [string] $luisAuthoringKey, + [string] $luisAuthoringRegion, + [string] $parametersFile, + [string] $languages = "en-us", + [string] $projDir = $(Get-Location), + [string] $logFile = $(Join-Path $PSScriptRoot .. "deploy_log.txt") +) + +# Reset log file +if (Test-Path $logFile) { + Clear-Content $logFile -Force | Out-Null +} +else { + New-Item -Path $logFile | Out-Null +} + +if (-not (Test-Path (Join-Path $projDir 'appsettings.json'))) +{ + Write-Host "! Could not find an 'appsettings.json' file in the current directory." -ForegroundColor DarkRed + Write-Host "+ Please re-run this script from your project directory." -ForegroundColor Magenta + Break +} + +# Get mandatory parameters +if (-not $name) { + $name = Read-Host "? Bot Name (used as default name for resource group and deployed resources)" +} + +if (-not $resourceGroup) { + $resourceGroup = $name +} + +if (-not $location) { + $location = Read-Host "? Azure resource group region" +} + +if (-not $appPassword) { + $appPassword = Read-Host "? Password for MSA app registration (must be at least 16 characters long, contain at least 1 special character, and contain at least 1 numeric character)" +} + +if (-not $luisAuthoringRegion) { + $luisAuthoringRegion = Read-Host "? LUIS Authoring Region (westus, westeurope, or australiaeast)" +} + +if (-not $luisAuthoringKey) { + Switch ($luisAuthoringRegion) { + "westus" { + $luisAuthoringKey = Read-Host "? LUIS Authoring Key (found at https://luis.ai/user/settings)" + Break + } + "westeurope" { + $luisAuthoringKey = Read-Host "? LUIS Authoring Key (found at https://eu.luis.ai/user/settings)" + Break + } + "australiaeast" { + $luisAuthoringKey = Read-Host "? LUIS Authoring Key (found at https://au.luis.ai/user/settings)" + Break + } + default { + Write-Host "! $($luisAuthoringRegion) is not a valid LUIS authoring region." -ForegroundColor DarkRed + Break + } + } + + if (-not $luisAuthoringKey) { + Break + } +} + +if (-not $appId) { + # Create app registration + $app = (az ad app create ` + --display-name $name ` + --password `"$($appPassword)`" ` + --available-to-other-tenants ` + --reply-urls 'https://token.botframework.com/.auth/web/redirect' ` + --output json) + + # Retrieve AppId + if ($app) { + $appId = ($app | ConvertFrom-Json) | Select-Object -ExpandProperty appId + } + + if(-not $appId) { + Write-Host "! Could not provision Microsoft App Registration automatically. Review the log for more information." -ForegroundColor DarkRed + Write-Host "! Log: $($logFile)" -ForegroundColor DarkRed + Write-Host "+ Provision an app manually in the Azure Portal, then try again providing the -appId and -appPassword arguments. See https://aka.ms/vamanualappcreation for more information." -ForegroundColor Magenta + Break + } +} + +# Get timestamp +$timestamp = Get-Date -f MMddyyyyHHmmss + +# Create resource group +Write-Host "> Creating resource group ..." +(az group create --name $resourceGroup --location $location --output json) 2>> $logFile | Out-Null + +# Deploy Azure services (deploys LUIS, QnA Maker, Content Moderator, CosmosDB) +if ($parametersFile) { + Write-Host "> Validating Azure deployment ..." + $validation = az group deployment validate ` + --resource-group $resourcegroup ` + --template-file "$(Join-Path $PSScriptRoot '..' 'Resources' 'template.json')" ` + --parameters "@$($parametersFile)" ` + --parameters name=$name microsoftAppId=$appId microsoftAppPassword="`"$($appPassword)`"" ` + --output json + + if ($validation) { + $validation >> $logFile + $validation = $validation | ConvertFrom-Json + + if (-not $validation.error) { + Write-Host "> Deploying Azure services (this could take a while)..." -ForegroundColor Yellow + $deployment = az group deployment create ` + --name $timestamp ` + --resource-group $resourceGroup ` + --template-file "$(Join-Path $PSScriptRoot '..' 'Resources' 'template.json')" ` + --parameters "@$($parametersFile)" ` + --parameters name=$name microsoftAppId=$appId microsoftAppPassword="`"$($appPassword)`"" ` + --output json + } + else { + Write-Host "! Template is not valid with provided parameters. Review the log for more information." -ForegroundColor DarkRed + Write-Host "! Error: $($validation.error.message)" -ForegroundColor DarkRed + Write-Host "! Log: $($logFile)" -ForegroundColor DarkRed + Write-Host "+ To delete this resource group, run 'az group delete -g $($resourceGroup) --no-wait'" -ForegroundColor Magenta + Break + } + } +} +else { + Write-Host "> Validating Azure deployment ..." + $validation = az group deployment validate ` + --resource-group $resourcegroup ` + --template-file "$(Join-Path $PSScriptRoot '..' 'Resources' 'template.json')" ` + --parameters name=$name microsoftAppId=$appId microsoftAppPassword="`"$($appPassword)`"" ` + --output json + + if ($validation) { + $validation >> $logFile + $validation = $validation | ConvertFrom-Json + + if (-not $validation.error) { + Write-Host "> Deploying Azure services (this could take a while)..." -ForegroundColor Yellow + $deployment = az group deployment create ` + --name $timestamp ` + --resource-group $resourceGroup ` + --template-file "$(Join-Path $PSScriptRoot '..' 'Resources' 'template.json')" ` + --parameters name=$name microsoftAppId=$appId microsoftAppPassword="`"$($appPassword)`"" ` + --output json + } + else { + Write-Host "! Template is not valid with provided parameters. Review the log for more information." -ForegroundColor DarkRed + Write-Host "! Error: $($validation.error.message)" -ForegroundColor DarkRed + Write-Host "! Log: $($logFile)" -ForegroundColor DarkRed + Write-Host "+ To delete this resource group, run 'az group delete -g $($resourceGroup) --no-wait'" -ForegroundColor Magenta + Break + } + } +} + +# Get deployment outputs +$outputs = (az group deployment show ` + --name $timestamp ` + --resource-group $resourceGroup ` + --query properties.outputs ` + --output json) 2>> $logFile + +# If it succeeded then we perform the remainder of the steps +if ($outputs) +{ + # Log and convert to JSON + $outputs >> $logFile + $outputs = $outputs | ConvertFrom-Json + $outputMap = @{} + $outputs.PSObject.Properties | Foreach-Object { $outputMap[$_.Name] = $_.Value } + + # Update appsettings.json + Write-Host "> Updating appsettings.json ..." + if (Test-Path $(Join-Path $projDir appsettings.json)) { + $settings = Get-Content $(Join-Path $projDir appsettings.json) | ConvertFrom-Json + } + else { + $settings = New-Object PSObject + } + + $settings | Add-Member -Type NoteProperty -Force -Name 'microsoftAppId' -Value $appId + $settings | Add-Member -Type NoteProperty -Force -Name 'microsoftAppPassword' -Value $appPassword + foreach ($key in $outputMap.Keys) { $settings | Add-Member -Type NoteProperty -Force -Name $key -Value $outputMap[$key].value } + $settings | ConvertTo-Json -depth 100 | Out-File $(Join-Path $projDir appsettings.json) + + if ($outputs.qnaMaker.value.key) { $qnaSubscriptionKey = $outputs.qnaMaker.value.key } + + # Delay to let QnA Maker finish setting up + Start-Sleep -s 30 + + # Deploy cognitive models + Invoke-Expression "& '$(Join-Path $PSScriptRoot 'deploy_cognitive_models.ps1')' -name $($name) -luisAuthoringRegion $($luisAuthoringRegion) -luisAuthoringKey $($luisAuthoringKey) -luisAccountName $($outputs.luis.value.accountName) -luisAccountRegion $($outputs.luis.value.region) -luisSubscriptionKey $($outputs.luis.value.key) -resourceGroup $($resourceGroup) -qnaSubscriptionKey '$($qnaSubscriptionKey)' -outFolder '$($projDir)' -languages '$($languages)'" + + # Publish bot + Invoke-Expression "& '$(Join-Path $PSScriptRoot 'publish.ps1')' -name $($outputs.botWebAppName.value) -resourceGroup $($resourceGroup) -projFolder '$($projDir)'" + + Write-Host "> Done." +} +else +{ + # Check for failed deployments + $operations = (az group deployment operation list -g $resourceGroup -n $timestamp --output json) 2>> $logFile | Out-Null + + if ($operations) { + $operations = $operations | ConvertFrom-Json + $failedOperations = $operations | Where { $_.properties.statusmessage.error -ne $null } + if ($failedOperations) { + foreach ($operation in $failedOperations) { + switch ($operation.properties.statusmessage.error.code) { + "MissingRegistrationForLocation" { + Write-Host "! Deployment failed for resource of type $($operation.properties.targetResource.resourceType). This resource is not avaliable in the location provided." -ForegroundColor DarkRed + Write-Host "+ Update the .\Deployment\Resources\parameters.template.json file with a valid region for this resource and provide the file path in the -parametersFile parameter." -ForegroundColor Magenta + } + default { + Write-Host "! Deployment failed for resource of type $($operation.properties.targetResource.resourceType)." + Write-Host "! Code: $($operation.properties.statusMessage.error.code)." + Write-Host "! Message: $($operation.properties.statusMessage.error.message)." + } + } + } + } + } + else { + Write-Host "! Deployment failed. Please refer to the log file for more information." -ForegroundColor DarkRed + Write-Host "! Log: $($logFile)" -ForegroundColor DarkRed + } + + Write-Host "+ To delete this resource group, run 'az group delete -g $($resourceGroup) --no-wait'" -ForegroundColor Magenta + Break +} \ No newline at end of file diff --git a/skills/src/csharp/experimental/eventskill/Deployment/Scripts/deploy_cognitive_models.ps1 b/skills/src/csharp/experimental/eventskill/Deployment/Scripts/deploy_cognitive_models.ps1 new file mode 100644 index 0000000000..361af45fa9 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Deployment/Scripts/deploy_cognitive_models.ps1 @@ -0,0 +1,300 @@ +#Requires -Version 6 + +Param( + [string] $name, + [string] $luisAuthoringRegion, + [string] $luisAuthoringKey, + [string] $luisAccountName, + [string] $luisAccountRegion, + [string] $luisSubscriptionKey, + [string] $qnaSubscriptionKey, + [string] $resourceGroup, + [switch] $useDispatch, + [string] $languages = "en-us", + [string] $outFolder = $(Get-Location), + [string] $logFile = $(Join-Path $PSScriptRoot .. "deploy_cognitive_models_log.txt") +) + +. $PSScriptRoot\luis_functions.ps1 +. $PSScriptRoot\qna_functions.ps1 + +# Reset log file +if (Test-Path $logFile) { + Clear-Content $logFile -Force | Out-Null +} +else { + New-Item -Path $logFile | Out-Null +} + +# Get mandatory parameters +if (-not $name) { + $name = Read-Host "? Base name for Cognitive Models" +} + +if (-not $luisAuthoringRegion) { + $luisAuthoringRegion = Read-Host "? LUIS Authoring Region (westus, westeurope, or australiaeast)" +} + +if (-not $luisAuthoringKey) { + Switch ($luisAuthoringRegion) { + "westus" { + $luisAuthoringKey = Read-Host "? LUIS Authoring Key (found at https://luis.ai/user/settings)" + Break + } + "westeurope" { + $luisAuthoringKey = Read-Host "? LUIS Authoring Key (found at https://eu.luis.ai/user/settings)" + Break + } + "australiaeast" { + $luisAuthoringKey = Read-Host "? LUIS Authoring Key (found at https://au.luis.ai/user/settings)" + Break + } + default { + Write-Host "! $($luisAuthoringRegion) is not a valid LUIS authoring region." -ForegroundColor DarkRed + Break + } + } + + if (-not $luisAuthoringKey) { + Break + } +} + +if (-not $luisAccountName) { + $luisAccountName = Read-Host "? LUIS Service Name (exising service in Azure required)" +} + +if (-not $resourceGroup) { + $resourceGroup = $name + + $rgExists = az group exists -n $resourceGroup --output json + if ($rgExists -eq "false") + { + $resourceGroup = Read-Host "? LUIS Service Resource Group (exising service in Azure required)" + } +} + +if (-not $luisSubscriptionKey) { + $keys = az cognitiveservices account keys list --name $luisAccountName --resource-group $resourceGroup --output json | ConvertFrom-Json + + if ($keys) { + $luisSubscriptionKey = $keys.key1 + } + else { + Write-Host "! Could not retrieve LUIS Subscription Key." -ForgroundColor DarkRed + Write-Host "+ Verify the -luisAccountName and -resourceGroup parameters are correct." -ForegroundColor Magenta + Break + } +} + +if (-not $luisAccountRegion) { + $luisAccountRegion = Read-Host "? LUIS Service Location" +} + +if (-not $qnaSubscriptionKey) { + $useQna = $false +} +else { + $useQna = $true +} + +$azAccount = az account show --output json | ConvertFrom-Json +$azAccessToken = $(Invoke-Expression "az account get-access-token --output json") | ConvertFrom-Json + +# Get languages +$languageArr = $languages -split "," + +# Initialize settings obj +$settings = @{ defaultLocale = $languageArr[0]; cognitiveModels = New-Object PSObject } + +# Deploy localized resources +Write-Host "> Deploying cognitive models ..." +foreach ($language in $languageArr) +{ + $langCode = ($language -split "-")[0] + $config = New-Object PSObject + + if ($useDispatch) { + # Add dispatch to config + $config | Add-Member -MemberType NoteProperty -Name dispatchModel -Value $(New-Object PSObject) + + # Initialize Dispatch + Write-Host "> Initializing dispatch model ..." + $dispatchName = "$($name)$($langCode)_Dispatch" + $dataFolder = Join-Path $PSScriptRoot .. Resources Dispatch $langCode + (dispatch init ` + --name $dispatchName ` + --luisAuthoringKey $luisAuthoringKey ` + --luisAuthoringRegion $luisAuthoringRegion ` + --dataFolder $dataFolder) 2>> $logFile | Out-Null + } + + # Deploy LUIS apps + $luisFiles = Get-ChildItem "$(Join-Path $PSScriptRoot .. 'Resources' 'LU' $langCode)" | Where {$_.extension -eq ".lu"} + if ($luisFiles) { + $config | Add-Member -MemberType NoteProperty -Name languageModels -Value @() + + foreach ($lu in $luisFiles) + { + # Deploy LUIS model + $luisApp = DeployLUIS ` + -name $name ` + -lu_file $lu ` + -region $luisAuthoringRegion ` + -luisAuthoringKey $luisAuthoringKey ` + -language $language ` + -log $logFile + + Write-Host "> Setting LUIS subscription key ..." + if ($luisApp) { + # Setting subscription key + $addKeyResult = luis add appazureaccount ` + --appId $luisApp.id ` + --authoringKey $luisAuthoringKey ` + --region $luisAuthoringRegion ` + --accountName $luisAccountName ` + --azureSubscriptionId $azAccount.id ` + --resourceGroup $resourceGroup ` + --armToken "$($azAccessToken.accessToken)" 2>> $logFile + + if (-not $addKeyResult) { + $luisKeySet = $false + Write-Host "! Could not assign subscription key automatically. Review the log for more information. " -ForegroundColor DarkRed + Write-Host "! Log: $($logFile)" -ForegroundColor DarkRed + Write-Host "+ Please assign your subscription key manually in the LUIS portal." -ForegroundColor Magenta + } + + if ($useDispatch) { + # Add luis app to dispatch + Write-Host "> Adding $($lu.BaseName) app to dispatch model ..." + (dispatch add ` + --type "luis" ` + --name $luisApp.name ` + --id $luisApp.id ` + --region $luisApp.region ` + --intentName "l_$($lu.BaseName)" ` + --dataFolder $dataFolder ` + --dispatch "$(Join-Path $dataFolder "$($dispatchName).dispatch")") 2>> $logFile | Out-Null + } + + # Add to config + $config.languageModels += @{ + id = $lu.BaseName + name = $luisApp.name + appid = $luisApp.id + authoringkey = $luisAuthoringKey + authoringRegion = $luisAuthoringRegion + subscriptionkey = $luisSubscriptionKey + version = $luisApp.activeVersion + region = $luisAccountRegion + } + } + else { + Write-Host "! Could not create LUIS app. Skipping dispatch add." -ForegroundColor Cyan + } + } + } + + if ($useQna) { + if (Test-Path $(Join-Path $PSScriptRoot .. 'Resources' 'QnA' $langCode)) { + # Deploy QnA Maker KBs + $qnaFiles = Get-ChildItem "$(Join-Path $PSScriptRoot .. 'Resources' 'QnA' $langCode)" -Recurse | Where {$_.extension -eq ".lu"} + + if ($qnaFiles) { + $config | Add-Member -MemberType NoteProperty -Name knowledgebases -Value @() + + foreach ($lu in $qnaFiles) + { + # Deploy QnA Knowledgebase + $qnaKb = DeployKB -name $name -lu_file $lu -qnaSubscriptionKey $qnaSubscriptionKey -log $logFile + + if ($qnaKb) { + if ($useDispatch) { + Write-Host "> Adding $($lu.BaseName) kb to dispatch model ..." + (dispatch add ` + --type "qna" ` + --name $qnaKb.name ` + --id $qnaKb.id ` + --key $qnaSubscriptionKey ` + --intentName "q_$($lu.BaseName)" ` + --dataFolder $dataFolder ` + --dispatch "$(Join-Path $dataFolder "$($dispatchName).dispatch")") 2>> $logFile | Out-Null + } + + # Add to config + $config.knowledgebases += @{ + id = $lu.BaseName + name = $qnaKb.name + kbId = $qnaKb.kbId + subscriptionKey = $qnaKb.subscriptionKey + endpointKey = $qnaKb.endpointKey + hostname = $qnaKb.hostname + } + } + else { + Write-Host "! Could not create knowledgebase. Skipping dispatch add." -ForegroundColor Cyan + } + } + } + } + else { + Write-Host "! No knowledgebases found. Skipping." -ForegroundColor Cyan + } + } + else { + Write-Host "! No QnA Maker Subscription Key provided. Skipping knowledgebases." -ForegroundColor Cyan + } + + if ($useDispatch) { + # Create dispatch model + Write-Host "> Creating dispatch model..." + $dispatch = (dispatch create ` + --dispatch "$(Join-Path $dataFolder "$($dispatchName).dispatch")" ` + --dataFolder $dataFolder ` + --culture $language) 2>> $logFile + + if (-not $dispatch) { + Write-Host "! Could not create Dispatch app. Review the log for more information." -ForegroundColor DarkRed + Write-Host "! Log: $($logFile)" -ForegroundColor DarkRed + Break + } + else { + $dispatchApp = $dispatch | ConvertFrom-Json + + # Setting subscription key + Write-Host "> Setting LUIS subscription key ..." + $addKeyResult = luis add appazureaccount ` + --appId $dispatchApp.appId ` + --accountName $luisAccountName ` + --authoringKey $luisAuthoringKey ` + --region $luisAuthoringRegion ` + --azureSubscriptionId $azAccount.id ` + --resourceGroup $resourceGroup ` + --armToken $azAccessToken.accessToken 2>> $logFile + + if (-not $addKeyResult) { + $luisKeySet = $false + Write-Host "! Could not assign subscription key automatically. Review the log for more information. " -ForegroundColor DarkRed + Write-Host "! Log: $($logFile)" -ForegroundColor DarkRed + Write-Host "+ Please assign your subscription key manually in the LUIS portal." -ForegroundColor Magenta + } + + # Add to config + $config.dispatchModel = @{ + type = "dispatch" + name = $dispatchApp.name + appid = $dispatchApp.appId + authoringkey = $luisauthoringkey + authoringRegion = $luisAuthoringRegion + subscriptionkey = $luisSubscriptionKey + region = $luisAccountRegion + } + } + } + + # Add config to cognitivemodels dictionary + $settings.cognitiveModels | Add-Member -Type NoteProperty -Force -Name $langCode -Value $config +} + +# Write out config to file +$settings | ConvertTo-Json -depth 100 | Out-File $(Join-Path $outFolder "cognitivemodels.json" ) \ No newline at end of file diff --git a/skills/src/csharp/experimental/eventskill/Deployment/Scripts/luis_functions.ps1 b/skills/src/csharp/experimental/eventskill/Deployment/Scripts/luis_functions.ps1 new file mode 100644 index 0000000000..0da139daa8 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Deployment/Scripts/luis_functions.ps1 @@ -0,0 +1,112 @@ +function DeployLUIS ($name, $lu_file, $region, $luisAuthoringKey, $language, $log) +{ + $id = $lu_file.BaseName + $outFile = "$($id).luis" + $outFolder = $lu_file.DirectoryName + $appName = "$($name)$($langCode)_$($id)" + + # Parse LU file + Write-Host "> Parsing $($id) LU file ..." + ludown parse toluis ` + --in $lu_file ` + --luis_culture $language ` + --out_folder $outFolder ` + --out $outFile + + # Create LUIS app + Write-Host "> Deploying $($id) LUIS app ..." + $luisApp = (luis import application ` + --appName $appName ` + --authoringKey $luisAuthoringKey ` + --subscriptionKey $luisAuthoringKey ` + --region $region ` + --in "$(Join-Path $outFolder $outFile)" ` + --wait) 2>> $log | ConvertFrom-Json + + if (-not $luisApp) { + Write-Host "! Could not deploy LUIS model. Review the log for more information." -ForegroundColor DarkRed + Write-Host "! Log: $($log)" -ForegroundColor DarkRed + Return $null + } + else { + # train and publish luis app + $(luis train version --appId $luisApp.id --region $region --authoringKey $luisAuthoringKey --versionId $luisApp.activeVersion --wait + & luis publish version --appId $luisApp.id --region $region --authoringKey $luisAuthoringKey --versionId $luisApp.activeVersion --wait) 2>> $log | Out-Null + + Return $luisApp + } +} + +function UpdateLUIS ($lu_file, $appId, $version, $region, $authoringKey, $subscriptionKey, $log) +{ + $id = $lu_file.BaseName + $outFile = "$($id).luis" + $outFolder = $lu_file.DirectoryName + + $luisApp = luis get application --appId $appId --region $region --authoringKey $authoringKey | ConvertFrom-Json + + # Parse LU file + Write-Host "> Parsing $($id) LU file ..." + ludown parse toluis ` + --in $lu_file ` + --luis_culture $luisApp.culture ` + --out_folder $outFolder ` + --out $outFile + + Write-Host "> Getting current versions ..." + # Get list of current versions + $versions = luis list versions ` + --appId $appId ` + --region $region ` + --authoringKey $authoringKey | ConvertFrom-Json + + # If the current version exists + if ($versions | Where { $_.version -eq $version }) + { + # delete any old backups + if ($versions | Where { $_.version -eq "backup" }) + { + Write-Host "> Deleting old backup version ..." + luis delete version ` + --appId $appId ` + --versionId "backup" ` + --region $region ` + --authoringKey $authoringKey ` + --force --wait | Out-Null + } + + # rename the active version to backup + Write-Host "> Saving current version as backup ..." + luis rename version ` + --appId $appId ` + --versionId $version ` + --region $region ` + --newVersionId backup ` + --authoringKey $authoringKey ` + --subscriptionKey $subscriptionKey ` + --wait | Out-Null + } + + # import the new 0.1 version from the .luis file + Write-Host "> Importing new version ..." + luis import version ` + --appId $appId ` + --versionId $version ` + --region $region ` + --authoringKey $authoringKey ` + --subscriptionKey $subscriptionKey ` + --in "$(Join-Path $outFolder $outFile)" ` + --wait | ConvertFrom-Json + + # train and publish luis app + $(luis train version --appId $appId --region $region --authoringKey $authoringKey --versionId $version --wait + & luis publish version --appId $appId --region $region --authoringKey $authoringKey --versionId $version --wait) 2>> $log | Out-Null +} + +function RunLuisGen($lu_file, $outName, $outFolder) { + $id = $lu_file.BaseName + $luisFolder = $lu_file.DirectoryName + $luisFile = Join-Path $luisFolder "$($id).luis" + + luisgen $luisFile -cs "$($outName)Luis" -o $outFolder +} \ No newline at end of file diff --git a/skills/src/csharp/experimental/eventskill/Deployment/Scripts/publish.ps1 b/skills/src/csharp/experimental/eventskill/Deployment/Scripts/publish.ps1 new file mode 100644 index 0000000000..9483ef1ce5 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Deployment/Scripts/publish.ps1 @@ -0,0 +1,66 @@ +#Requires -Version 6 + +Param( + [string] $name, + [string] $resourceGroup, + [string] $projFolder = $(Get-Location), + [string] $logFile = $(Join-Path $PSScriptRoot .. "publish_log.txt") +) + +# Get mandatory parameters +if (-not $name) { + $name = Read-Host "? Bot Web App Name" +} + +if (-not $resourceGroup) { + $resourceGroup = Read-Host "? Bot Resource Group" +} + +# Reset log file +if (Test-Path $logFile) { + Clear-Content $logFile -Force | Out-Null +} +else { + New-Item -Path $logFile | Out-Null +} + +# Check for existing deployment files +if (-not (Test-Path (Join-Path $projFolder '.deployment'))) { + + # Get path to csproj file + $projFile = Get-ChildItem $projFolder ` + | Where-Object {$_.extension -eq ".csproj" } ` + | Select-Object -First 1 + + # Add needed deployment files for az + az bot prepare-deploy --lang Csharp --code-dir $projFolder --proj-file-path $projFile.name --output json | Out-Null +} + +# Delete src zip, if it exists +$zipPath = $(Join-Path $projFolder 'code.zip') +if (Test-Path $zipPath) { + Remove-Item $zipPath -Force | Out-Null +} + +# Perform dotnet publish step ahead of zipping up +$publishFolder = $(Join-Path $projFolder 'bin\Release\netcoreapp2.2') +dotnet publish -c release -o $publishFolder -v q > $logFile + +if($?) +{ + # Compress source code + Get-ChildItem -Path "$($publishFolder)" | Compress-Archive -DestinationPath "$($zipPath)" -Force | Out-Null + + # Publish zip to Azure + Write-Host "> Publishing to Azure ..." -ForegroundColor Green + (az webapp deployment source config-zip ` + --resource-group $resourceGroup ` + --name $name ` + --src $zipPath ` + --output json) 2>> $logFile | Out-Null +} +else +{ + Write-Host "! Could not deploy automatically to Azure. Review the log for more information." -ForegroundColor DarkRed + Write-Host "! Log: $($logFile)" -ForegroundColor DarkRed +} \ No newline at end of file diff --git a/skills/src/csharp/experimental/eventskill/Deployment/Scripts/qna_functions.ps1 b/skills/src/csharp/experimental/eventskill/Deployment/Scripts/qna_functions.ps1 new file mode 100644 index 0000000000..0da139daa8 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Deployment/Scripts/qna_functions.ps1 @@ -0,0 +1,112 @@ +function DeployLUIS ($name, $lu_file, $region, $luisAuthoringKey, $language, $log) +{ + $id = $lu_file.BaseName + $outFile = "$($id).luis" + $outFolder = $lu_file.DirectoryName + $appName = "$($name)$($langCode)_$($id)" + + # Parse LU file + Write-Host "> Parsing $($id) LU file ..." + ludown parse toluis ` + --in $lu_file ` + --luis_culture $language ` + --out_folder $outFolder ` + --out $outFile + + # Create LUIS app + Write-Host "> Deploying $($id) LUIS app ..." + $luisApp = (luis import application ` + --appName $appName ` + --authoringKey $luisAuthoringKey ` + --subscriptionKey $luisAuthoringKey ` + --region $region ` + --in "$(Join-Path $outFolder $outFile)" ` + --wait) 2>> $log | ConvertFrom-Json + + if (-not $luisApp) { + Write-Host "! Could not deploy LUIS model. Review the log for more information." -ForegroundColor DarkRed + Write-Host "! Log: $($log)" -ForegroundColor DarkRed + Return $null + } + else { + # train and publish luis app + $(luis train version --appId $luisApp.id --region $region --authoringKey $luisAuthoringKey --versionId $luisApp.activeVersion --wait + & luis publish version --appId $luisApp.id --region $region --authoringKey $luisAuthoringKey --versionId $luisApp.activeVersion --wait) 2>> $log | Out-Null + + Return $luisApp + } +} + +function UpdateLUIS ($lu_file, $appId, $version, $region, $authoringKey, $subscriptionKey, $log) +{ + $id = $lu_file.BaseName + $outFile = "$($id).luis" + $outFolder = $lu_file.DirectoryName + + $luisApp = luis get application --appId $appId --region $region --authoringKey $authoringKey | ConvertFrom-Json + + # Parse LU file + Write-Host "> Parsing $($id) LU file ..." + ludown parse toluis ` + --in $lu_file ` + --luis_culture $luisApp.culture ` + --out_folder $outFolder ` + --out $outFile + + Write-Host "> Getting current versions ..." + # Get list of current versions + $versions = luis list versions ` + --appId $appId ` + --region $region ` + --authoringKey $authoringKey | ConvertFrom-Json + + # If the current version exists + if ($versions | Where { $_.version -eq $version }) + { + # delete any old backups + if ($versions | Where { $_.version -eq "backup" }) + { + Write-Host "> Deleting old backup version ..." + luis delete version ` + --appId $appId ` + --versionId "backup" ` + --region $region ` + --authoringKey $authoringKey ` + --force --wait | Out-Null + } + + # rename the active version to backup + Write-Host "> Saving current version as backup ..." + luis rename version ` + --appId $appId ` + --versionId $version ` + --region $region ` + --newVersionId backup ` + --authoringKey $authoringKey ` + --subscriptionKey $subscriptionKey ` + --wait | Out-Null + } + + # import the new 0.1 version from the .luis file + Write-Host "> Importing new version ..." + luis import version ` + --appId $appId ` + --versionId $version ` + --region $region ` + --authoringKey $authoringKey ` + --subscriptionKey $subscriptionKey ` + --in "$(Join-Path $outFolder $outFile)" ` + --wait | ConvertFrom-Json + + # train and publish luis app + $(luis train version --appId $appId --region $region --authoringKey $authoringKey --versionId $version --wait + & luis publish version --appId $appId --region $region --authoringKey $authoringKey --versionId $version --wait) 2>> $log | Out-Null +} + +function RunLuisGen($lu_file, $outName, $outFolder) { + $id = $lu_file.BaseName + $luisFolder = $lu_file.DirectoryName + $luisFile = Join-Path $luisFolder "$($id).luis" + + luisgen $luisFile -cs "$($outName)Luis" -o $outFolder +} \ No newline at end of file diff --git a/skills/src/csharp/experimental/eventskill/Deployment/Scripts/update_cognitive_models.ps1 b/skills/src/csharp/experimental/eventskill/Deployment/Scripts/update_cognitive_models.ps1 new file mode 100644 index 0000000000..d435017c30 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Deployment/Scripts/update_cognitive_models.ps1 @@ -0,0 +1,159 @@ +#Requires -Version 6 + +Param( + [switch] $RemoteToLocal, + [switch] $useLuisGen = $true, + [string] $configFile = $(Join-Path (Get-Location) 'cognitivemodels.json'), + [string] $dispatchFolder = $(Join-Path $PSScriptRoot '..' 'Resources' 'Dispatch'), + [string] $luisFolder = $(Join-Path $PSScriptRoot '..' 'Resources' 'LU'), + [string] $qnaFolder = $(Join-Path $PSScriptRoot '..' 'Resources' 'QnA'), + [string] $lgOutFolder = $(Join-Path (Get-Location) 'Services'), + [string] $logFile = $(Join-Path $PSScriptRoot .. "update_cognitive_models_log.txt") +) + +. $PSScriptRoot\luis_functions.ps1 +. $PSScriptRoot\qna_functions.ps1 + +# Reset log file +if (Test-Path $logFile) { + Clear-Content $logFile -Force | Out-Null +} +else { + New-Item -Path $logFile | Out-Null +} + +Write-Host "> Getting config file ..." +$languageMap = @{ } +$config = Get-Content -Raw -Path $configFile | ConvertFrom-Json +$config.cognitiveModels.PSObject.Properties | Foreach-Object { $languageMap[$_.Name] = $_.Value } + +foreach ($langCode in $languageMap.Keys) { + $models = $languageMap[$langCode] + $dispatch = $models.dispatchModel + + if ($RemoteToLocal) { + # Update local LU files based on hosted models + foreach ($luisApp in $models.languageModels) { + $culture = (luis get application ` + --appId $luisApp.appId ` + --authoringKey $luisApp.authoringKey ` + --subscriptionKey $luisApp.subscriptionKey ` + --region $luisApp.authoringRegion | ConvertFrom-Json).culture + + Write-Host "> Updating local $($luisApp.id).lu file ..." + luis export version ` + --appId $luisApp.appid ` + --versionId $luisApp.version ` + --region $luisApp.authoringRegion ` + --authoringKey $luisApp.authoringKey | ludown refresh ` + --stdin ` + -n "$($luisApp.id).lu" ` + -o $(Join-Path $luisFolder $langCode) + + # Parse LU file + $id = $luisApp.id + $outFile = "$($id).luis" + $outFolder = $(Join-Path $luisFolder $langCode) + $appName = "$($name)$($langCode)_$($id)" + + Write-Host "> Parsing $($luisApp.id) LU file ..." + ludown parse toluis ` + --in $(Join-Path $outFolder "$($luisApp.id).lu") ` + --luis_culture $culture ` + --out_folder $(Join-Path $luisFolder $langCode) ` + --out "$($luisApp.id).luis" + + if ($useLuisGen) { + Write-Host "> Running LuisGen for $($luisApp.id) app ..." + $luPath = $(Join-Path $luisFolder $langCode "$($luisApp.id).lu") + RunLuisGen -lu_file $(Get-Item $luPath) -outName "$($luisApp.id)" -outFolder $lgOutFolder + } + + # Add the LUIS application to the dispatch model. + # If the LUIS application id already exists within the model no action will be taken + if ($dispatch) { + Write-Host "> Adding $($luisApp.id) app to dispatch model ... " + (dispatch add ` + --type "luis" ` + --name $luisApp.name ` + --id $luisApp.appid ` + --region $luisApp.authoringRegion ` + --intentName "l_$($luisApp.id)" ` + --dispatch $(Join-Path $dispatchFolder $langCode "$($dispatch.name).dispatch") ` + --dataFolder $(Join-Path $dispatchFolder $langCode)) 2>> $logFile | Out-Null + } + } + + # Update local LU files based on hosted QnA KBs + foreach ($kb in $models.knowledgebases) { + Write-Host "> Updating local $($kb.id).lu file ..." + qnamaker export kb ` + --environment Prod ` + --kbId $kb.kbId ` + --subscriptionKey $kb.subscriptionKey | ludown refresh ` + --stdin ` + -n "$($kb.id).lu" ` + -o $(Join-Path $qnaFolder $langCode) + + # Add the knowledge base to the dispatch model. + # If the knowledge base id already exists within the model no action will be taken + if ($dispatch) { + Write-Host "> Adding $($kb.id) kb to dispatch model ..." + (dispatch add ` + --type "qna" ` + --name $kb.name ` + --id $kb.kbId ` + --key $kb.subscriptionKey ` + --intentName "q_$($kb.id)" ` + --dispatch $(Join-Path $dispatchFolder $langCode "$($dispatch.name).dispatch") ` + --dataFolder $(Join-Path $dispatchFolder $langCode)) 2>> $logFile | Out-Null + } + } + } + else { + # Update each luis model based on local LU files + foreach ($luisApp in $models.languageModels) { + Write-Host "> Updating hosted $($luisApp.id) app..." + $lu = Get-Item -Path $(Join-Path $luisFolder $langCode "$($luisApp.id).lu") + UpdateLUIS ` + -lu_file $lu ` + -appId $luisApp.appid ` + -version $luisApp.version ` + -region $luisApp.authoringRegion ` + -authoringKey $luisApp.authoringKey ` + -subscriptionKey $app.subscriptionKey + + if ($useLuisGen) { + Write-Host "> Running LuisGen for $($luisApp.id) app ..." + $luPath = $(Join-Path $luisFolder $langCode "$($luisApp.id).lu") + RunLuisGen -lu_file $(Get-Item $luPath) -outName "$($luisApp.id)" -outFolder $lgOutFolder + } + } + + # Update each knowledgebase based on local LU files + foreach ($kb in $models.knowledgebases) { + Write-Host "> Updating hosted $($kb.id) kb..." + $lu = Get-Item -Path $(Join-Path $qnaFolder $langCode "$($kb.id).lu") + UpdateKB ` + -lu_file $lu ` + -kbId $kb.kbId ` + -qnaSubscriptionKey $kb.subscriptionKey + } + } + + if ($dispatch) { + # Update dispatch model + Write-Host "> Updating dispatch model ..." + dispatch refresh ` + --dispatch $(Join-Path $dispatchFolder $langCode "$($dispatch.name).dispatch") ` + --dataFolder $(Join-Path $dispatchFolder $langCode) 2>> $logFile | Out-Null + + if ($useLuisGen) { + # Update dispatch.cs file + Write-Host "> Running LuisGen for Dispatch app..." + luisgen $(Join-Path $dispatchFolder $langCode "$($dispatch.name).json") -cs "DispatchLuis" -o $lgOutFolder 2>> $logFile | Out-Null + } + } +} + +Write-Host "> Done." \ No newline at end of file diff --git a/skills/src/csharp/experimental/eventskill/Dialogs/MainDialog.cs b/skills/src/csharp/experimental/eventskill/Dialogs/MainDialog.cs new file mode 100644 index 0000000000..e086eff611 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Dialogs/MainDialog.cs @@ -0,0 +1,246 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Threading; +using System.Threading.Tasks; +using EventSkill.Models; +using EventSkill.Responses.Main; +using EventSkill.Responses.Shared; +using EventSkill.Services; +using Luis; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Builder.Skills; +using Microsoft.Bot.Builder.Skills.Models; +using Microsoft.Bot.Builder.Solutions; +using Microsoft.Bot.Builder.Solutions.Dialogs; +using Microsoft.Bot.Builder.Solutions.Responses; +using Microsoft.Bot.Schema; + +namespace EventSkill.Dialogs +{ + public class MainDialog : RouterDialog + { + private BotSettings _settings; + private BotServices _services; + private ResponseManager _responseManager; + private IStatePropertyAccessor _stateAccessor; + private IStatePropertyAccessor _contextAccessor; + + public MainDialog( + BotSettings settings, + BotServices services, + ResponseManager responseManager, + UserState userState, + ConversationState conversationState, + IBotTelemetryClient telemetryClient) + : base(nameof(MainDialog), telemetryClient) + { + _settings = settings; + _services = services; + _responseManager = responseManager; + TelemetryClient = telemetryClient; + + // Initialize state accessor + _stateAccessor = conversationState.CreateProperty(nameof(EventSkillState)); + _contextAccessor = userState.CreateProperty(nameof(SkillContext)); + + // Register dialogs + } + + protected override async Task OnStartAsync(DialogContext dc, CancellationToken cancellationToken = default(CancellationToken)) + { + var locale = CultureInfo.CurrentUICulture; + await dc.Context.SendActivityAsync(_responseManager.GetResponse(MainResponses.WelcomeMessage)); + } + + protected override async Task RouteAsync(DialogContext dc, CancellationToken cancellationToken = default(CancellationToken)) + { + // get current activity locale + var locale = CultureInfo.CurrentUICulture.TwoLetterISOLanguageName; + var localeConfig = _services.CognitiveModelSets[locale]; + + // Populate state from SemanticAction as required + await PopulateStateFromSemanticAction(dc.Context); + + // Get skill LUIS model from configuration + localeConfig.LuisServices.TryGetValue("EventSkill", out var luisService); + + if (luisService == null) + { + throw new Exception("The specified LUIS Model could not be found in your Bot Services configuration."); + } + else + { + var turnResult = EndOfTurn; + var result = await luisService.RecognizeAsync(dc.Context, CancellationToken.None); + var intent = result?.TopIntent().intent; + + switch (intent) + { + + case EventLuis.Intent.None: + { + // No intent was identified, send confused message + await dc.Context.SendActivityAsync(_responseManager.GetResponse(SharedResponses.DidntUnderstandMessage)); + turnResult = new DialogTurnResult(DialogTurnStatus.Complete); + break; + } + + default: + { + // intent was identified but not yet implemented + await dc.Context.SendActivityAsync(_responseManager.GetResponse(MainResponses.FeatureNotAvailable)); + turnResult = new DialogTurnResult(DialogTurnStatus.Complete); + break; + } + } + + if (turnResult != EndOfTurn) + { + await CompleteAsync(dc); + } + } + } + + protected override async Task CompleteAsync(DialogContext dc, DialogTurnResult result = null, CancellationToken cancellationToken = default(CancellationToken)) + { + var response = dc.Context.Activity.CreateReply(); + response.Type = ActivityTypes.EndOfConversation; + await dc.Context.SendActivityAsync(response); + await dc.EndDialogAsync(result); + } + + protected override async Task OnEventAsync(DialogContext dc, CancellationToken cancellationToken = default(CancellationToken)) + { + switch (dc.Context.Activity.Name) + { + case TokenEvents.TokenResponseEventName: + { + // Auth dialog completion + var result = await dc.ContinueDialogAsync(); + + // If the dialog completed when we sent the token, end the skill conversation + if (result.Status != DialogTurnStatus.Waiting) + { + var response = dc.Context.Activity.CreateReply(); + response.Type = ActivityTypes.EndOfConversation; + + await dc.Context.SendActivityAsync(response); + } + + break; + } + } + } + + protected override async Task OnInterruptDialogAsync(DialogContext dc, CancellationToken cancellationToken = default(CancellationToken)) + { + var result = InterruptionAction.NoAction; + + if (dc.Context.Activity.Type == ActivityTypes.Message) + { + // get current activity locale + var locale = CultureInfo.CurrentUICulture.TwoLetterISOLanguageName; + var localeConfig = _services.CognitiveModelSets[locale]; + + // check general luis intent + localeConfig.LuisServices.TryGetValue("General", out var luisService); + + if (luisService == null) + { + throw new Exception("The specified LUIS Model could not be found in your Skill configuration."); + } + else + { + var luisResult = await luisService.RecognizeAsync(dc.Context, cancellationToken); + var topIntent = luisResult.TopIntent(); + + if (topIntent.score > 0.5) + { + switch (topIntent.intent) + { + case GeneralLuis.Intent.Cancel: + { + result = await OnCancel(dc); + break; + } + + case GeneralLuis.Intent.Help: + { + result = await OnHelp(dc); + break; + } + + case GeneralLuis.Intent.Logout: + { + result = await OnLogout(dc); + break; + } + } + } + } + } + + return result; + } + + private async Task OnCancel(DialogContext dc) + { + await dc.Context.SendActivityAsync(_responseManager.GetResponse(MainResponses.CancelMessage)); + await CompleteAsync(dc); + await dc.CancelAllDialogsAsync(); + return InterruptionAction.StartedDialog; + } + + private async Task OnHelp(DialogContext dc) + { + await dc.Context.SendActivityAsync(_responseManager.GetResponse(MainResponses.HelpMessage)); + return InterruptionAction.MessageSentToUser; + } + + private async Task OnLogout(DialogContext dc) + { + BotFrameworkAdapter adapter; + var supported = dc.Context.Adapter is BotFrameworkAdapter; + if (!supported) + { + throw new InvalidOperationException("OAuthPrompt.SignOutUser(): not supported by the current adapter"); + } + else + { + adapter = (BotFrameworkAdapter)dc.Context.Adapter; + } + + await dc.CancelAllDialogsAsync(); + + // Sign out user + var tokens = await adapter.GetTokenStatusAsync(dc.Context, dc.Context.Activity.From.Id); + foreach (var token in tokens) + { + await adapter.SignOutUserAsync(dc.Context, token.ConnectionName); + } + + await dc.Context.SendActivityAsync(_responseManager.GetResponse(MainResponses.LogOut)); + + return InterruptionAction.StartedDialog; + } + + private async Task PopulateStateFromSemanticAction(ITurnContext context) + { + // Example of populating local state with data passed through semanticAction out of Activity + var activity = context.Activity; + var semanticAction = activity.SemanticAction; + //if (semanticAction != null && semanticAction.Entities.ContainsKey("location")) + //{ + // var location = semanticAction.Entities["location"]; + // var locationObj = location.Properties["location"].ToString(); + // var state = await _stateAccessor.GetAsync(context, () => new SkillState()); + // state.CurrentCoordinates = locationObj; + //} + } + } +} \ No newline at end of file diff --git a/skills/src/csharp/experimental/eventskill/Dialogs/SkillDialogBase.cs b/skills/src/csharp/experimental/eventskill/Dialogs/SkillDialogBase.cs new file mode 100644 index 0000000000..e8010354c4 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Dialogs/SkillDialogBase.cs @@ -0,0 +1,175 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Threading; +using System.Threading.Tasks; +using EventSkill.Models; +using EventSkill.Responses.Shared; +using EventSkill.Services; +using Luis; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Builder.Skills; +using Microsoft.Bot.Builder.Solutions.Authentication; +using Microsoft.Bot.Builder.Solutions.Responses; +using Microsoft.Bot.Builder.Solutions.Util; +using Microsoft.Bot.Schema; + +namespace EventSkill.Dialogs +{ + public class SkillDialogBase : ComponentDialog + { + public SkillDialogBase( + string dialogId, + BotSettings settings, + BotServices services, + ResponseManager responseManager, + ConversationState conversationState, + IBotTelemetryClient telemetryClient) + : base(dialogId) + { + Services = services; + ResponseManager = responseManager; + StateAccessor = conversationState.CreateProperty(nameof(EventSkillState)); + TelemetryClient = telemetryClient; + + // NOTE: Uncomment the following if your skill requires authentication + // if (!settings.OAuthConnections.Any()) + // { + // throw new Exception("You must configure an authentication connection before using this component."); + // } + + // AddDialog(new MultiProviderAuthDialog(settings.OAuthConnections)); + } + + protected BotSettings Settings { get; set; } + + protected BotServices Services { get; set; } + + protected IStatePropertyAccessor StateAccessor { get; set; } + + protected ResponseManager ResponseManager { get; set; } + + protected override async Task OnBeginDialogAsync(DialogContext dc, object options, CancellationToken cancellationToken = default(CancellationToken)) + { + await GetLuisResult(dc); + return await base.OnBeginDialogAsync(dc, options, cancellationToken); + } + + protected override async Task OnContinueDialogAsync(DialogContext dc, CancellationToken cancellationToken = default(CancellationToken)) + { + await GetLuisResult(dc); + return await base.OnContinueDialogAsync(dc, cancellationToken); + } + + protected async Task GetAuthToken(WaterfallStepContext sc, CancellationToken cancellationToken) + { + try + { + return await sc.PromptAsync(nameof(MultiProviderAuthDialog), new PromptOptions()); + } + catch (SkillException ex) + { + await HandleDialogExceptions(sc, ex); + return new DialogTurnResult(DialogTurnStatus.Cancelled, CommonUtil.DialogTurnResultCancelAllDialogs); + } + catch (Exception ex) + { + await HandleDialogExceptions(sc, ex); + return new DialogTurnResult(DialogTurnStatus.Cancelled, CommonUtil.DialogTurnResultCancelAllDialogs); + } + } + + protected async Task AfterGetAuthToken(WaterfallStepContext sc, CancellationToken cancellationToken = default(CancellationToken)) + { + try + { + // When the user authenticates interactively we pass on the tokens/Response event which surfaces as a JObject + // When the token is cached we get a TokenResponse object. + if (sc.Result is ProviderTokenResponse providerTokenResponse) + { + var state = await StateAccessor.GetAsync(sc.Context); + state.Token = providerTokenResponse.TokenResponse.Token; + } + + return await sc.NextAsync(); + } + catch (SkillException ex) + { + await HandleDialogExceptions(sc, ex); + return new DialogTurnResult(DialogTurnStatus.Cancelled, CommonUtil.DialogTurnResultCancelAllDialogs); + } + catch (Exception ex) + { + await HandleDialogExceptions(sc, ex); + return new DialogTurnResult(DialogTurnStatus.Cancelled, CommonUtil.DialogTurnResultCancelAllDialogs); + } + } + + // Validators + protected Task TokenResponseValidator(PromptValidatorContext pc, CancellationToken cancellationToken) + { + var activity = pc.Recognized.Value; + if (activity != null && activity.Type == ActivityTypes.Event) + { + return Task.FromResult(true); + } + else + { + return Task.FromResult(false); + } + } + + protected Task AuthPromptValidator(PromptValidatorContext promptContext, CancellationToken cancellationToken) + { + var token = promptContext.Recognized.Value; + if (token != null) + { + return Task.FromResult(true); + } + else + { + return Task.FromResult(false); + } + } + + // Helpers + protected async Task GetLuisResult(DialogContext dc) + { + if (dc.Context.Activity.Type == ActivityTypes.Message) + { + var state = await StateAccessor.GetAsync(dc.Context, () => new EventSkillState()); + + // Get luis service for current locale + var locale = CultureInfo.CurrentUICulture.TwoLetterISOLanguageName; + var localeConfig = Services.CognitiveModelSets[locale]; + var luisService = localeConfig.LuisServices["EventSkill"]; + + // Get intent and entities for activity + var result = await luisService.RecognizeAsync(dc.Context, CancellationToken.None); + state.LuisResult = result; + } + } + + // This method is called by any waterfall step that throws an exception to ensure consistency + protected async Task HandleDialogExceptions(WaterfallStepContext sc, Exception ex) + { + // send trace back to emulator + var trace = new Activity(type: ActivityTypes.Trace, text: $"DialogException: {ex.Message}, StackTrace: {ex.StackTrace}"); + await sc.Context.SendActivityAsync(trace); + + // log exception + TelemetryClient.TrackException(ex, new Dictionary { { nameof(sc.ActiveDialog), sc.ActiveDialog?.Id } }); + + // send error message to bot user + await sc.Context.SendActivityAsync(ResponseManager.GetResponse(SharedResponses.ErrorMessage)); + + // clear state + var state = await StateAccessor.GetAsync(sc.Context); + state.Clear(); + } + } +} \ No newline at end of file diff --git a/skills/src/csharp/experimental/eventskill/EventSkill.csproj b/skills/src/csharp/experimental/eventskill/EventSkill.csproj new file mode 100644 index 0000000000..5dbe7dc7cd --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/EventSkill.csproj @@ -0,0 +1,166 @@ + + + + netcoreapp2.2 + NU1701 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Always + + + + + + TextTemplatingFileGenerator + MainResponses.cs + + + TextTemplatingFileGenerator + SharedResponses.cs + + + + + + + + + + True + True + general.lu + + + True + True + general.lu + + + True + True + skill.lu + + + True + True + general.lu + + + True + True + skill.lu + + + True + True + general.lu + + + True + True + skill.lu + + + True + True + deploy.ps1 + + + True + True + deploy_cognitive_models.ps1 + + + True + True + MyTemplate.vstemplate + + + True + True + readme.md + + + True + True + MainResponses.tt + + + True + True + ResponseIdCollection.t4 + + + True + True + SharedResponses.tt + + + True + True + __TemplateIcon.ico + + + + diff --git a/skills/src/csharp/experimental/eventskill/Models/EventSkillState.cs b/skills/src/csharp/experimental/eventskill/Models/EventSkillState.cs new file mode 100644 index 0000000000..f4e69b193d --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Models/EventSkillState.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Luis; + +namespace EventSkill.Models +{ + public class EventSkillState + { + public string Token { get; set; } + + public EventLuis LuisResult { get; set; } + + public void Clear() + { + } + } +} diff --git a/skills/src/csharp/experimental/eventskill/Pipeline/EventSkill.yml b/skills/src/csharp/experimental/eventskill/Pipeline/EventSkill.yml new file mode 100644 index 0000000000..07158db945 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Pipeline/EventSkill.yml @@ -0,0 +1,61 @@ +# specific branch build +trigger: + branches: + include: + - master + - feature/* + +pool: + name: Hosted VS2017 + demands: + - msbuild + - visualstudio + +variables: + buildPlatform: 'Any CPU' + buildConfiguration: 'Release' + +steps: +- task: DotNetCoreInstaller@0 + displayName: 'Use .NET Core sdk 2.2.100' + inputs: + version: 2.2.100 + continueOnError: true + +- task: NuGetToolInstaller@0 + displayName: 'Use NuGet 4.9.1' + inputs: + versionSpec: 4.9.1 + +- task: NuGetCommand@2 + displayName: 'NuGet restore' + inputs: + # if your working directory is not root, you may change the following path + restoreSolution: 'EventSkill.sln' + +- task: VSBuild@1 + displayName: 'Build solution EventSkill.sln' + inputs: + # if your working directory is not root, you may change the following path + solution: EventSkill.sln + vsVersion: '16.0' + platform: '$(buildPlatform)' + configuration: '$(buildConfiguration)' + +- task: DotNetCoreCLI@2 + displayName: 'test results' + inputs: + command: test + # if your working directory is not root, you may change the following path + projects: '$(System.DefaultWorkingDirectory)\EventSkill.Tests.csproj' + arguments: '-v n --configuration $(buildConfiguration) --no-build --no-restore --filter TestCategory!=IgnoreInAutomatedBuild /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura' + # if your working directory is not root, you may change the following path + workingDirectory: 'EventSkill.Tests' + +- task: PublishCodeCoverageResults@1 + displayName: 'Publish code coverage' + inputs: + codeCoverageTool: Cobertura + # if your working directory is not root, you may change the following path + summaryFileLocation: '$(Build.SourcesDirectory)\EventSkill.Tests\coverage.cobertura.xml' + reportDirectory: '$(Build.SourcesDirectory)\EventSkill.Tests' \ No newline at end of file diff --git a/skills/src/csharp/experimental/eventskill/Program.cs b/skills/src/csharp/experimental/eventskill/Program.cs new file mode 100644 index 0000000000..b39c536c59 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Program.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; + +namespace EventSkill +{ + public class Program + { + public static void Main(string[] args) + { + BuildWebHost(args).Run(); + } + + public static IWebHost BuildWebHost(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup() // Note: Application Insights is added in Startup. Disabling is also handled there. + .Build(); + } +} diff --git a/skills/src/csharp/experimental/eventskill/Properties/launchSettings.json b/skills/src/csharp/experimental/eventskill/Properties/launchSettings.json new file mode 100644 index 0000000000..913e8a92a4 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:3978/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "EventSkill": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:1205/" + } + } +} \ No newline at end of file diff --git a/skills/src/csharp/experimental/eventskill/Responses/Main/MainResponses.cs b/skills/src/csharp/experimental/eventskill/Responses/Main/MainResponses.cs new file mode 100644 index 0000000000..d5bc028fcb --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Responses/Main/MainResponses.cs @@ -0,0 +1,23 @@ +// https://docs.microsoft.com/en-us/visualstudio/modeling/t4-include-directive?view=vs-2017 +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Bot.Builder.Solutions.Responses; + +namespace EventSkill.Responses.Main +{ + /// + /// Contains bot responses. + /// + public class MainResponses : IResponseIdCollection + { + // Generated accessors + public const string WelcomeMessage = "WelcomeMessage"; + public const string HelpMessage = "HelpMessage"; + public const string GreetingMessage = "GreetingMessage"; + public const string GoodbyeMessage = "GoodbyeMessage"; + public const string LogOut = "LogOut"; + public const string FeatureNotAvailable = "FeatureNotAvailable"; + public const string CancelMessage = "CancelMessage"; + } +} \ No newline at end of file diff --git a/skills/src/csharp/experimental/eventskill/Responses/Main/MainResponses.de.json b/skills/src/csharp/experimental/eventskill/Responses/Main/MainResponses.de.json new file mode 100644 index 0000000000000000000000000000000000000000..ef03ebc6620830304b210358aeabd8fc913fc6d8 GIT binary patch literal 4004 zcmds)%TC)s6o${bQr}_fHRS=i>y1#Ug+&)#L@d%^NQ8-_cmlnMx9xMZ{r=-Pu`v=@ z%>}BFPdo)5^i`&J>!l zpDoYymmGCQqe)e*Ch(p@H?WHZ_r&_vW92K)1iH`8H`oUonQqc-Vv%`nFYO4*q3?|S z9obKznApCZz;Ul7hi*=wPwgAOrL^n4v$AES<4r?`lUp*$0&}b@`xY+Osl7&*h!#0| z6bl*q=+o3DaXqI{M?3WSN*`He2*;`OnZR9EE6*lNd2VVP6L`q;(?{uZ>6h6V5^m4u zUG99UKJR%we?+-FO zsp>r^hOe28d8g!*z}LXv`Z`0-3`teL%R0T^AX z(Qe_L$_z_XT}~Hktn<#zTccR`J76JoA!!XYksj^g9ok)b-_O0RVSQ!Ox$Xdc2Y7w% z$UhILx+1%v zSC{-5bK!YpznDj!Dz<~YVICQ}?aEK7%T=dd@9OuQL-bss9zYmTK2e^~_YY_S*<;3z zu%oX2;fVQvG_GTI`&H(x`$hjIvYpiHWAG-#2^W!nEJ8$;O@zy9_Jm8Vb-_nhzT5YL IZY(>BzsC(9sQ>@~ literal 0 HcmV?d00001 diff --git a/skills/src/csharp/experimental/eventskill/Responses/Main/MainResponses.es.json b/skills/src/csharp/experimental/eventskill/Responses/Main/MainResponses.es.json new file mode 100644 index 0000000000000000000000000000000000000000..27a60042dbaf412a290b97d0a0c18bffb66a8e10 GIT binary patch literal 3860 zcmdUy&2H0B5QS%r#5)+Tt3Cj`f}lm>PXdWW)x~Y>q^86+u@ga2A1061B@0&USODL- zj)RRstxJB0toY~p&iI_UXJ&4GeE(#3?T!T;p}n!#-r2x18*}%JU%}yQ#9e6jne%IQ zWijDRj!oj{R&$P*9yLct+o~Q7@ttGm?0n%pv}1c>sg*VXLu6CC0FA?gv-doG;jZ8s zczmhH_7)ET-)~wC@Q~pn;e3Mcy$&K_2$>bz2hJ7lb$z{#Z_WF%h34cpbc)K9h%4rW zM;O@wQ7Andajv7>wNw;xtV=suM5Jcs@G!t*?){{ASDdT4;S$ef151bxsUiQbbnxfZ zk3cGoe1v<{(#tAV|2E&;)ipmPYhn(mO)A+&4Vbf*2X^MOy~=?ryVoO&|7PD^lWtwO zd8L|@9~t@KdSYu@?E<1E#&uVmLn5lWe@Z5Ar~k}RUWE3)QfK>Vu5H(hn(F>_HGj30 z`zcWz*$dwZst(ZBb?CEvm3{j)-%@W@zAAbtPeaC3m(^nvDoVyXXZt-exu4f=%>O07 zk9;2)f_%bm!=Z`{F4`N3+vg>xK79K4TrHbdekIO4Cw0qW!6fEdF4*;9a#Md@7R`}$ z*OSLEb?}>dy+3E|O7eTgSmG=9Dd$b?Y#$MGMY_t1l(^S<;_RE(RO%I0cf7#YA?=W* zxP+Q0tjZ?oPkkq#M>@vEC1JKSy3goKEGArsjLiJ~9#5(;+V5890Ke+=BVwZw=Pewb zMjpl3uE5(z5xdH-smIM%>W4<F_2=8+)w>=t7#1LSpKi{*MXG7!?$ZRXb~GM0%U? S=AxG>ZtH*ZtD-MfUHT^j%M&jE literal 0 HcmV?d00001 diff --git a/skills/src/csharp/experimental/eventskill/Responses/Main/MainResponses.fr.json b/skills/src/csharp/experimental/eventskill/Responses/Main/MainResponses.fr.json new file mode 100644 index 0000000000000000000000000000000000000000..ab07ecc7fdf429c745c7318b7e3be543e6ae65e5 GIT binary patch literal 4040 zcmd^?&2G~`5Xa|?#5*j$aH)6zZdC=P;-iO3aEN*^i78eS$BLb(fcmc7AQv71`2Ba| zI3|%ID~+QFS&laAnc4q*&;I%S%^uq$b3BoKu*8Pex3P^_z2z-h-W>cBDK zOAb%!X#-7fDN!_fnq1XxgzOx?*lrinBkS1<%d9lX{R-y{Ea9K?m})%E2KJP51N9i% z6y&L0BkN=hfzFJOl-M1=0p~-VR@tx8>%MXN^*?B&^%RTBriDi|uv3tiU>~DRu|U^D zlcpY#*EWZ{v{!yzYZ4=&kHp-|$&h}5tUwzr@!YRSjF2I3=SSh^7<~ue?ZV5O&N@~f zpZiQomQittoW3B+oPFU0Rp$y!L!Vb|kTtoE0nsYPeOH%r zs@a&F{fOre(@ReAsPpl!E|Ripo@;d*0ilqb9+*j;Sx7GF3qkhuip0&vT{UKQQ#Xs^Il&c5?15(8CjN%j5&+O}5N{j~jCpo)uT>`3GJ&iZbXy{_Hr&c5s4 zOMDumr?MfjclfKmpL}lC^JWGX&F>+9GiK^AmpbZJ z?BiKoThS^#G{`{OzI_Hw!Iw4}^gr2THSW9bT4&yKx}6LUa@V-D*W7!m YnAFWSvmZWw+jXLCa=N#&P7D*sUo;IjRR910 literal 0 HcmV?d00001 diff --git a/skills/src/csharp/experimental/eventskill/Responses/Main/MainResponses.it.json b/skills/src/csharp/experimental/eventskill/Responses/Main/MainResponses.it.json new file mode 100644 index 0000000000000000000000000000000000000000..830683e337ef8baec5f007b1d2a7fba392115711 GIT binary patch literal 3948 zcmds)%}(1u5Xa|Ssqf(W(hK4NdP7l5kSMh3p$EjlBu;AKIKgpHKg7Foq>NKJ_NFo_3qBh{%3Z+{OjirducB$;)(6ErIy>krZ(a2J-<0mVq@N7djrji+H;ib z%;3o!t*1G=mna%NZLWGXM)nN8#4eZ8V>`1WD_BphMCKeU8T?naaAU?vkyXs5jKWys z8?&#q4DJfvsSTm3crv?YcFqZV9b7^ZLzCK9{yqB5VyJCh+u){!>HH6jvd{wS%l>nR zb7%+HL|RRqoob7BIc15Nvq67pm0G_92?Hd~oSXvbvbm>?@A2F;u*BAfK8NNh`os|Z zBfKng*4VBu{O#H~+wRmvmplh^FR7kc*E?(9`SG_N?icJF3{f6r8J?F)9u?KTKa zn`^y@df-XO3t`q-@pRENS2mqt4neN0ePQ45v0eDq0jDco!z|e5YFbP={-ON7-Yk6# z_OYG1Jm<50+n-|>!RxHoN3DuH!2e2Dr*hU+kMH=Yk}sCEykd1fAbKAwzk3h=6YG6f zJt7;SNlD+4prf=?r&KViO8c6sek4r8$H-omJ@3#^nlD^@oS=0@pLOkSJ?H-dZ{Ai1{1Fts+e^5U5wpU+a~rye1RNRIZrLf^ zijS~_neM8DB62xQowYbJu&YX?tSi^}UV0^8x@q4_-Dm#;v?b#RS?Zis^CP6Gr +<#@ output extension=".cs" #> +<#@ include file="..\Shared\ResponseIdCollection.t4"#> \ No newline at end of file diff --git a/skills/src/csharp/experimental/eventskill/Responses/Main/MainResponses.zh.json b/skills/src/csharp/experimental/eventskill/Responses/Main/MainResponses.zh.json new file mode 100644 index 0000000000..647d005144 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Responses/Main/MainResponses.zh.json @@ -0,0 +1,82 @@ +{ + "WelcomeMessage": { + "replies": [ + { + "text": "[在此输入您的简介信息]", + "speak": "[在此输入您的简介信息]" + } + ], + "inputHint": "acceptingInput" + }, + "HelpMessage": { + "replies": [ + { + "text": "[在此处输入您的帮助信息]", + "speak": "[在此处输入您的帮助信息]" + } + ], + "suggestedActions": [], + "inputHint": "acceptingInput" + }, + "GreetingMessage": { + "replies": [ + { + "text": "嗨!", + "speak": "嗨!" + }, + { + "text": "我在这!", + "speak": "我在这!" + }, + { + "text": "你好!", + "speak": "你好!" + }, + { + "text": "您好!", + "speak": "您好!" + } + ], + "inputHint": "acceptingInput" + }, + "GoodbyeMessage": { + "replies": [ + { + "text": "再见!", + "speak": "再见!" + } + ], + "inputHint": "acceptingInput" + }, + "LogOut": { + "replies": [ + { + "text": "您已成功退出。", + "speak": "您已成功退出。" + }, + { + "text": "您已退出。", + "speak": "您已退出。" + } + ], + "inputHint": "acceptingInput" + }, + "FeatureNotAvailable": { + "replies": [ + { + "text": "此功能在此技能中尚不可用。", + "speak": "此功能在此技能中尚不可用。" + } + ], + "inputHint": "acceptingInput" + }, + "CancelMessage": { + "replies": [ + { + "text": "好的, 我们从头再来。", + "speak": "好的, 我们从头再来。" + } + ], + "inputHint": "acceptingInput" + } +} diff --git a/skills/src/csharp/experimental/eventskill/Responses/Shared/ResponseIdCollection.t4 b/skills/src/csharp/experimental/eventskill/Responses/Shared/ResponseIdCollection.t4 new file mode 100644 index 0000000000..d6c0d7cc3e --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Responses/Shared/ResponseIdCollection.t4 @@ -0,0 +1,31 @@ +<#@ assembly name="Newtonsoft.Json.dll" #> +<# + var className = System.IO.Path.GetFileNameWithoutExtension(Host.TemplateFile); + var namespaceName = System.Runtime.Remoting.Messaging.CallContext.LogicalGetData("NamespaceHint"); + string myFile = System.IO.File.ReadAllText(this.Host.ResolvePath(className + ".json")); + var json = Newtonsoft.Json.JsonConvert.DeserializeObject>(myFile); + var responses = string.Empty; + var cards = string.Empty; +#> +// https://docs.microsoft.com/en-us/visualstudio/modeling/t4-include-directive?view=vs-2017 +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Bot.Builder.Solutions.Responses; + +namespace <#= namespaceName #> +{ + /// + /// Contains bot responses. + /// + public class <#= className #> : IResponseIdCollection + { + // Generated accessors +<# +// This code runs in the text json: +foreach (var propertyName in json) { +#> + public const string <#= propertyName.Key.Substring(0, 1).ToUpperInvariant() + propertyName.Key.Substring(1) #> = "<#= propertyName.Key #>"; +<# } #> + } +} \ No newline at end of file diff --git a/skills/src/csharp/experimental/eventskill/Responses/Shared/SharedResponses.cs b/skills/src/csharp/experimental/eventskill/Responses/Shared/SharedResponses.cs new file mode 100644 index 0000000000..1abffee9b1 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Responses/Shared/SharedResponses.cs @@ -0,0 +1,22 @@ +// https://docs.microsoft.com/en-us/visualstudio/modeling/t4-include-directive?view=vs-2017 +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Bot.Builder.Solutions.Responses; + +namespace EventSkill.Responses.Shared +{ + /// + /// Contains bot responses. + /// + public class SharedResponses : IResponseIdCollection + { + // Generated accessors + public const string DidntUnderstandMessage = "DidntUnderstandMessage"; + public const string CancellingMessage = "CancellingMessage"; + public const string NoAuth = "NoAuth"; + public const string AuthFailed = "AuthFailed"; + public const string ActionEnded = "ActionEnded"; + public const string ErrorMessage = "ErrorMessage"; + } +} \ No newline at end of file diff --git a/skills/src/csharp/experimental/eventskill/Responses/Shared/SharedResponses.de.json b/skills/src/csharp/experimental/eventskill/Responses/Shared/SharedResponses.de.json new file mode 100644 index 0000000000000000000000000000000000000000..7573b415f8c8dbfec8e1ec2b50b74ae5ebdc3f80 GIT binary patch literal 11500 zcmdT~+inv{5UppV{fA+m5g)({%0dtkR|&xbt9@`1#}2VgWIM?c#JBECzJVvS%Q;o9 zbdP5e+?lpL$nveHd#1WBr%u((|NniJKjfYic$M;0j^$7$GMD%GJe0mnWrlw}{r3w0 zXL$8wfM2CN!kVz!&M?K1QyJkroNIIby1oME@Ks&qqa{|HVqH&87As5H#aICe&hRc| zjPp+MJ;E$X`T)O==L-p8;B$IaW!Zoz_QI+ylKu}C+i&ugI<@*P^4z!xXbo_n-=e$X5JzN*_x#8n|j zh!BhEi9Am5_T1OatChOtV_YCooWTFa8ue97I8?1T8W8`exf9jh1inunwmDU8_OOl! zHpO}O)$@slX*%te)Oju9{zBA@J@pWpHAJ}rLNM+N{T#`a;yy>xwAlVid3XLNykU+P zGmYoZW)1ur5mV4F_jU7X<>|uio@+xck0W)Uad!#L4K+J2M{bHf@?9~nCfr|}2r@%R~=mG37!9Ozh@|r)#T(p!Y8rxh&j%cg4QDu-b(bJe|nCm?*PAbgm z0<)N#nB%V$&8JCRwB1!=yO7z(nslbWI;&rn#rIa-vFjn;aiLY#K&@q*GbKiHUpKE- zUcdwBg@wF@rOD!0X^zz=Se@{Q^FVSGvKGT2TgSOE$2L6wsArvPEKIPT{P8A4tD{AV zcDpKnUMsy(8+=i#jFdynpy8NLh0H;}X3v7h8QB;5t2)%*nu>B?->b< zksWw{=%%r=#@LzTo6%w2pzYctkJNcB(p)zMG_w3Wf-VQzr#EbBiV)hu`=D{Okl9;a zCptNUYhOQaM#j9_b{rqek85Vcn#NIPb#2EQUOf{8{ionjx1_FHBpylO4!nQFdtL`X z39Eni;K8?7&CVO10aeIX`^e;*xf{p(gS50uz-&*skMO?KV^eS%*8cP;W6sVM$QT}C zJkBijYA5gvzsuCr5l!)&uE-pEAYQGvui$A5@17d(Vnym&W%qmx`;f^k$2Y6i7*W$L zQ|$>f*Rv^h@(Azxe&R@P&kc<|6!KEDn^je;Sjqc%Pr4pOwA2S2;a%-eosySZcPdxh zY+kauX5*0*RJ}_ta%X|TpMhVyc=PFpP2uWGAh~xbmJu*dFN0Z~b=my-$%oMMx2{#2 zen(g3kNqb{tXg%v4_y*%*WdlvTh!z2uUOYaJhJZC+67v%pE^aifcWJ1ur|Y5-szR6 zVjV-nN9MymEJ&Q4;W)dtynD}F$)`}5N7?_a#*CpcS03RA@tOU{GyJo@#|7>Su*$oi z@D#;%q>HWJJ~DPyU!J$Df+*w}G%-|u%G%iUM4io5NA?teb(zB5apx{^W^-oXmJ4@Z zZtA0_@QyCZzFzBu9B{io8*gOPGImmYdReD;zv4PH5jYjAmplE$U1lk&;L!I GKllkMcE0og literal 0 HcmV?d00001 diff --git a/skills/src/csharp/experimental/eventskill/Responses/Shared/SharedResponses.es.json b/skills/src/csharp/experimental/eventskill/Responses/Shared/SharedResponses.es.json new file mode 100644 index 0000000000000000000000000000000000000000..8839f0125a39c440532d5fdae0a46085487ae3de GIT binary patch literal 10194 zcmd5?O>fgc5ZyBp{~`E{`UALBr4+SNL6v|L;@~D}8j&xYw4z1*G5nJL7LFWx>K%A* zCR1->CwP-}5><8LwY@w0cIM5Sb^iYO;hwu^u7SVAy>VUFasxMUpKxrsw#(cYXQ`aM z!}%D0sq5fd;$GpI{AukW!_k4aDf%oul_9<&k@WcqurS9wW$;6$yA?_bb zf3$1pUgCTpt!=TC$Ci6QYfoCgxhwS1aQd0yN{TbirHNiLw}-x87A&F-3Hool?>MU6 zb&YO#+q~~e?5U2>`w}ctL5b_u!@RCBt6O|_pYY<9Y>F3=A)-X>TKdwKE3^^0K8^!i z%jB$uUM}R!&=0|L7i^7-{=GXB7R>G*1p30HU#Hgo_!3fU*;59BHD>LXyT5iI?6O+3SHR*$|GFkygKd#N4+jL74F=%+iRl3p$^+sn6-*k4zbkw<&CEKC5y^U(wJM{ukMC48H@LHf&yZ-0 zKXP1TW~0(Li2j&Leb1_&SP%uRVtss4Tnj-X&S*sh{Fb^i3uEFy^3>&Y7h{$K6U1@c z9h!2z%beDQuhO%*M(VSV68rRL^IqnUhGZ793c1x3xcWA0KJH0{uH|N}@>h8MToi8m zNU&p7W4m8gFSCt#+t9V_&3M@qvN5dNZG<&Dl9}nx^x`HkXPz4)7I_?s&SN}Ggr`Gz zDYieXW^LRRD60sRFO&sl86)?g5s);D^PHCnpO`ic-JzIQF){6ki`qxwQ>p;6qs4m8 zwac-y`|IO|ZP`~u*>4HSx!nJ!(r?0}v@SbA951itEZ@Eb3wK2!w>Fe}kH9t5nk8e5_d)(fjzNn02{tT z>4Nw8&V+R$y;*D0e8<8n1dE2%Jh-0GiwOBGAsn^Jj?=-E(Wpv}@J~OfoATS&arZ;8LA>&L;7`1??HVCtxNSM9$zOTVRV zU(PekHug+^F8ED?YE<2S$h8@HtRp#uzRJB`avH0#o_i7SrZ?`W{%Wo7r5_N_x?61f zge|BL>JH7GeDh5nH?2}NzptAeH*Hs3R1cPT4iQvMkWlDbUaN-R(D3i|T9cxL$(C^jARKa~|Vq(_!;( d+^gyLvNZ|G=DRzC;!61~b>v6(e!|<$ literal 0 HcmV?d00001 diff --git a/skills/src/csharp/experimental/eventskill/Responses/Shared/SharedResponses.fr.json b/skills/src/csharp/experimental/eventskill/Responses/Shared/SharedResponses.fr.json new file mode 100644 index 0000000000000000000000000000000000000000..18875ffabba1e1c35e1116322eb1b2e227793788 GIT binary patch literal 11096 zcmdU#&2ke*6oq@8s=ULX4I7dNU_(K0Do~J+O2Q_U#b606kYz`*U4|6znq49md4W`s zoUgB1-O^}`+HPq?sU*#`x~K1)d+zV(zmI>oFWnce(yQklx}od4u{(3W=(FzzZsMjo ztNqziolo_u-76jS++D4StL+LC{c`F?x(;(~&aaCdn8Ry%RT%AQ#i`cS?#+B963sMv zqASka9j$z=-z!PO=b^@rwSqmzS~b+nO6RrzKJ;^bcjx~5LUYgkxE+~2QU(14oZhdIb8|!=dpySOwnr?*E1(IEV2v)$b=P0H5Bdb7 zN^fJpTOCb2dZrK1z0g>#Z!o>BZ`0DT#>T?<@A(zL`=wqpe-793u-{kt%WCPF=PQW5 z6aJCC>+#1@4QI32d=1>oV1E~~l|{FWL+~BH0;k(O2Rey}BmW{(v3+7= z;NtduahEcdMZ3*zBRDHqRY$AoG22teXf@N{-#2 zHc<|zJyV@gv7{)63TE?aWfRG)4m1yq)~QSI z&r~qmMkROevgkb1T~hN}|0Ij9JH8 zXV{))ajFa1%A)(g+Z`yHrBSI)RgHnjP@{B0z?c4kWntDQNl|Bp)eA5DYG#A5X^blw z-t=Aa>fmmqD~)i|*S!`FV0$j4;P%MD%c)1)Vxe)q4bNc~OV|0AG7>bAN=LRG&Au12 z?t3dfns=?1eQwWNcgarObLkD85j~CZ(f8mb$9fGEU$G+W1FNp_A92dmg$- z(G$T1{1D8*GflJf+>Lap#%~@AKUHn5qP$!Q*YaZKdqQW!cuod|rA>wDc6G3?@_(LM zI*~2SeAK|2@R8VCh}z3lM@}xdD=p>9Usg+Pwb0c*%W|j??Xj12HWaTxwT32G5p&Uz z^n@km_oABp;JDYS_1m8Gf=bF;RtwLCgjFq|6f&i&@2Pt~wqH^=HIk0xUpC6z%w#OH|W zr8wKp)dQcI)$XmILkzN2zj%mXmM@TYuXL48VP^A+>tSFM!ue-|9z1+<;Tr!>akGRUALgjZcPNzk!{<| zfjkF&!ijq-?7IFxk9j_Onn4CZHz3rf_B~phN8V!(bOUN-9F^8smOECYzm@SnhSmQT zZ9|q+vo~iu|H^{zR22As%k8JigwQ0|Sr53cU%=9Oz^mB}{oh!PTqiPbTaL1*{U|gB zVoKoECOo3+oAM)vSEcqVi^31$4t;03)g>&AEbwO`3zuNxrH)pawp{lHrsdSV`z3W* zjimevPXx@q!+#jVGn?>?AKS+*tm80+TJv6nIj%*{&a{c%I`SSmk C-k~G_ literal 0 HcmV?d00001 diff --git a/skills/src/csharp/experimental/eventskill/Responses/Shared/SharedResponses.it.json b/skills/src/csharp/experimental/eventskill/Responses/Shared/SharedResponses.it.json new file mode 100644 index 0000000000000000000000000000000000000000..5316e5df39dc5d67e2c86738b2fa29e87f78f1ba GIT binary patch literal 10412 zcmd6t&u-H|5XSe6#5)9ExS&1&7laB34pc=ITo4B*4XH(&g!GRPsPD(4aOTDZ_b6?0veyHCq2Gk4@h?$Xcs zNY_o>Os|1E(f@(I$u+)^eReeS;|A_Z$EUi_mA)Hj6@^wYc6++#qlP8So#_r^_eJk! zZ|YeCzG0o$x7g7hk%NC?074)2EqgZB=Y>{2*ZWLJJz=v-+u*UX|Y z>f4UAs2zDQ0EuRXpTXu z?fufOB19ehJCW7KHzg-q)ZtBJn9*`=19{fek2WSJ|J)UgaqMo`Qz+QEaT+MWPDL zW)XzaLD>~iUb1~lo5)wsd{|LbnbtS;`S!xS^q)&@my2gcLSgQCbt+zZbu%`*(T7!y zmbv{UKUo&!-V?VCHM}po7d|VKYv+Dv$+)>!bf`acp5nJr~hC-hHO({-AuT zhz2FxUiIE(4|`I$uO16{og6ds2*t2rw^`Dz_&}?m+A_6d3^CN+jf$FJKg^0r-WwFSZ8Qo{eeIekV)^QdgEit&$?D&OD0nCzYvr7JAq!V$eI$ zXOJH1of>K)9F3=83(zh6F{4aT$9UwX?MjPkJ)S_vumM3ZvpV(P2r>rGj6p2roJ};U zXZp@MWc8=sXHJ%#i8^0q-XGI;TRbf>zx}UdI^j3$$CSyM)rkbZ~Z4qgGfm45c?jCBs&|`_&`EA=-^@r|pbmXytmHMpt zyUo&EarI2!G4Wgs4s$HF(oufxa|leuZ!h33w4HSqiIt)3q7n%eXWhs720pZI81cnU zQ>m~X-XBD$>UQ!v8+TA3E{+qtGx rd7-CfdfGbo;Wt$1YpZ{GTGFl_>8h%C#op#FRL}I0E4lx*e=hqMDlPu+ literal 0 HcmV?d00001 diff --git a/skills/src/csharp/experimental/eventskill/Responses/Shared/SharedResponses.json b/skills/src/csharp/experimental/eventskill/Responses/Shared/SharedResponses.json new file mode 100644 index 0000000000..f29bd17fe5 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Responses/Shared/SharedResponses.json @@ -0,0 +1,148 @@ +{ + "DidntUnderstandMessage": { + "replies": [ + { + "text": "Sorry, I didn't understand what you meant.", + "speak": "Sorry, I didn't understand what you meant." + }, + { + "text": "I didn't understand, perhaps try again in a different way.", + "speak": "I didn't understand, perhaps try again in a different way." + }, + { + "text": "Can you try to ask in a different way?", + "speak": "Can you try to ask in a different way?" + }, + { + "text": "I didn't get what you mean, can you try in a different way?", + "speak": "I didn't get what you mean, can you try in a different way?" + }, + { + "text": "Could you elaborate?", + "speak": "Could you elaborate?" + }, + { + "text": "Please say that again in a different way.", + "speak": "Please say that again in a different way." + }, + { + "text": "I didn't quite get that.", + "speak": "I didn't quite get that." + }, + { + "text": "Can you say that in a different way?", + "speak": "Can you say that in a different way?" + }, + { + "text": "Can you try to ask me again? I didn't get what you mean.", + "speak": "Can you try to ask me again? I didn't get what you mean." + } + ], + "inputHint": "acceptingInput" + }, + "CancellingMessage": { + "replies": [ + { + "text": "Sure, we can do this later.", + "speak": "Sure, we can do this later." + }, + { + "text": "Sure, we can start this later.", + "speak": "Sure, we can start this later." + }, + { + "text": "No problem, you can try again at another time.", + "speak": "No problem, you can try again at another time." + }, + { + "text": "Alright, let me know when you need my help.", + "speak": "Alright, let me know when you need my help." + }, + { + "text": "Sure, I'm here if you need me.", + "speak": "Sure, I'm here if you need me." + } + ], + "inputHint": "acceptingInput" + }, + "NoAuth": { + "replies": [ + { + "text": "Please log in before taking further action.", + "speak": "Please log in before taking further action." + }, + { + "text": "Please log in so I can take further action.", + "speak": "Please log in so I can take further action." + }, + { + "text": "Please log in so I can proceed with your request.", + "speak": "Please log in so I can proceed with your request." + }, + { + "text": "Can you log in so I can help you out further?", + "speak": "Can you log in so I can help you out further?" + }, + { + "text": "You need to log in so I can take further action.", + "speak": "You need to log in so I can take further action." + } + ], + "inputHint": "expectingInput" + }, + "AuthFailed": { + "replies": [ + { + "text": "Authentication failed. Please try again", + "speak": "Authentication failed. Please try again." + }, + { + "text": "You failed to log in. Please try again later.", + "speak": "You failed to log in. please try again later." + }, + { + "text": "Your log in failed. Let's try this again.", + "speak": "Your log in failed. Let's try this again." + } + ], + "inputHint": "acceptingInput" + }, + "ActionEnded": { + "replies": [ + { + "text": "Let me know if you need my help with something else.", + "speak": "Let me know if you need my help with something else." + }, + { + "text": "I'm here if you need me.", + "speak": "I'm here if you need me." + } + ], + "inputHint": "acceptingInput" + }, + "ErrorMessage": { + "replies": [ + { + "text": "Sorry, it looks like something went wrong!", + "speak": "Sorry, it looks like something went wrong!" + }, + { + "text": "An error occurred, please try again later.", + "speak": "An error occurred, please try again later." + }, + { + "text": "Something went wrong, sorry!", + "speak": "Something went wrong, sorry!" + }, + { + "text": "It seems like something went wrong. Can you try again later?", + "speak": "It seems like something went wrong. Can you try again later?" + }, + { + "text": "Sorry I can't help right now. Please try again later.", + "speak": "Sorry I can't help right now. Please try again later." + } + ], + "inputHint": "acceptingInput" + } +} diff --git a/skills/src/csharp/experimental/eventskill/Responses/Shared/SharedResponses.tt b/skills/src/csharp/experimental/eventskill/Responses/Shared/SharedResponses.tt new file mode 100644 index 0000000000..f204f0981b --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Responses/Shared/SharedResponses.tt @@ -0,0 +1,3 @@ +<#@ template debug="false" hostspecific="true" language="C#" #> +<#@ output extension=".cs" #> +<#@ include file="..\Shared\ResponseIdCollection.t4"#> \ No newline at end of file diff --git a/skills/src/csharp/experimental/eventskill/Responses/Shared/SharedResponses.zh.json b/skills/src/csharp/experimental/eventskill/Responses/Shared/SharedResponses.zh.json new file mode 100644 index 0000000000..cc6a668c56 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Responses/Shared/SharedResponses.zh.json @@ -0,0 +1,100 @@ +{ + "DidntUnderstandMessage": { + "replies": [ + { + "text": "对不起,没有明白你的意思。", + "speak": "对不起,没有明白你的意思。" + }, + { + "text": "我没有明白你的意思,你能以不同的方式尝试吗?", + "speak": "我没有明白你的意思,你能以不同的方式尝试吗?" + }, + { + "text": "你能以不同的方式说出来吗?", + "speak": "你能以不同的方式说出来吗?" + }, + { + "text": "你能再试一次问我,我没听懂你的意思。", + "speak": "你能再试一次问我,我没听懂你的意思。" + } + ], + "inputHint": "acceptingInput" + }, + "CancellingMessage": { + "replies": [ + { + "text": "当然,我们可以稍后再做。", + "speak": "当然,我们可以稍后再做。" + } + ], + "inputHint": "acceptingInput" + }, + "NoAuth": { + "replies": [ + { + "text": "请登录,以便我采取进一步行动。", + "speak": "请登录,以便我采取进一步行动。" + } + ], + "inputHint": "expectingInput" + }, + "AuthFailed": { + "replies": [ + { + "text": "您的登录失败,请稍后再试。", + "speak": "您的登录失败,请稍后再试。" + } + ], + "inputHint": "acceptingInput" + }, + "ActionEnded": { + "replies": [ + { + "text": "还有什么需要我的帮助吗?", + "speak": "还有什么需要我的帮助吗?" + }, + { + "text": "还有什么我可以帮你的吗?", + "speak": "还有什么我可以帮你的吗?" + }, + { + "text": "还有什么想要采取行动吗?", + "speak": "还有什么想要采取行动吗?" + }, + { + "text": "如果您需要我的帮助,请告诉我。", + "speak": "如果您需要我的帮助,请告诉我。" + }, + { + "text": "如果你需要我的帮助,我就在这里。", + "speak": "如果你需要我的帮助,我就在这里。" + } + ], + "inputHint": "acceptingInput" + }, + "ErrorMessage": { + "replies": [ + { + "text": "对不起,看起来出了问题!", + "speak": "对不起,看起来出了问题!" + }, + { + "text": "发生错误,给我一些时间,稍后再试。", + "speak": "发生错误,给我一些时间,稍后再试。" + }, + { + "text": "出了点问题,对不起!", + "speak": "出了点问题,对不起!" + }, + { + "text": "我相信出了点问题。你稍后可以再试一次吗?", + "speak": "我相信出了点问题。你稍后可以再试一次吗?" + }, + { + "text": "抱歉,我现在找不到你想要的东西。请稍后再试。", + "speak": "抱歉,我现在找不到你想要的东西。请稍后再试。" + } + ], + "inputHint": "acceptingInput" + } +} diff --git a/skills/src/csharp/experimental/eventskill/Services/BotServices.cs b/skills/src/csharp/experimental/eventskill/Services/BotServices.cs new file mode 100644 index 0000000000..d15e4c70e9 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Services/BotServices.cs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.AI.Luis; +using Microsoft.Bot.Builder.AI.QnA; +using Microsoft.Bot.Builder.Solutions; + +namespace EventSkill.Services +{ + public class BotServices + { + public BotServices() + { + } + + public BotServices(BotSettings settings, IBotTelemetryClient client) + { + foreach (var pair in settings.CognitiveModels) + { + var set = new CognitiveModelSet(); + var language = pair.Key; + var config = pair.Value; + + var telemetryClient = client; + var luisOptions = new LuisPredictionOptions() + { + TelemetryClient = telemetryClient, + LogPersonalInformation = true, + }; + + if (config.DispatchModel != null) + { + var dispatchApp = new LuisApplication(config.DispatchModel.AppId, config.DispatchModel.SubscriptionKey, config.DispatchModel.GetEndpoint()); + set.DispatchService = new LuisRecognizer(dispatchApp); + } + + if (config.LanguageModels != null) + { + foreach (var model in config.LanguageModels) + { + var luisApp = new LuisApplication(model.AppId, model.SubscriptionKey, model.GetEndpoint()); + set.LuisServices.Add(model.Id, new LuisRecognizer(luisApp)); + } + } + + if (config.Knowledgebases != null) + { + foreach (var kb in config.Knowledgebases) + { + var qnaEndpoint = new QnAMakerEndpoint() + { + KnowledgeBaseId = kb.KbId, + EndpointKey = kb.EndpointKey, + Host = kb.Hostname, + }; + var qnaMaker = new QnAMaker(qnaEndpoint); + set.QnAServices.Add(kb.Id, qnaMaker); + } + } + + CognitiveModelSets.Add(language, set); + } + } + + public Dictionary CognitiveModelSets { get; set; } = new Dictionary(); + } +} \ No newline at end of file diff --git a/skills/src/csharp/experimental/eventskill/Services/BotSettings.cs b/skills/src/csharp/experimental/eventskill/Services/BotSettings.cs new file mode 100644 index 0000000000..f520ce7124 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Services/BotSettings.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Bot.Builder.Solutions; + +namespace EventSkill.Services +{ + public class BotSettings : BotSettingsBase + { + } +} \ No newline at end of file diff --git a/skills/src/csharp/experimental/eventskill/Services/EventLuis.cs b/skills/src/csharp/experimental/eventskill/Services/EventLuis.cs new file mode 100644 index 0000000000..063c28ffad --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Services/EventLuis.cs @@ -0,0 +1,64 @@ +// +// Code generated by LUISGen +// Tool github: https://github.com/microsoft/botbuilder-tools +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. +// +using Newtonsoft.Json; +using System.Collections.Generic; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.AI.Luis; +namespace Luis +{ + public class EventLuis : IRecognizerConvert + { + public string Text; + public string AlteredText; + public enum Intent + { + GetEvents, + None + }; + public Dictionary Intents; + + public class _Entities + { + + // Instance + public class _Instance + { + } + [JsonProperty("$instance")] + public _Instance _instance; + } + public _Entities Entities; + + [JsonExtensionData(ReadData = true, WriteData = true)] + public IDictionary Properties { get; set; } + + public void Convert(dynamic result) + { + var app = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(result)); + Text = app.Text; + AlteredText = app.AlteredText; + Intents = app.Intents; + Entities = app.Entities; + Properties = app.Properties; + } + + public (Intent intent, double score) TopIntent() + { + Intent maxIntent = Intent.None; + var max = 0.0; + foreach (var entry in Intents) + { + if (entry.Value.Score > max) + { + maxIntent = entry.Key; + max = entry.Value.Score.Value; + } + } + return (maxIntent, max); + } + } +} diff --git a/skills/src/csharp/experimental/eventskill/Services/GeneralLuis.cs b/skills/src/csharp/experimental/eventskill/Services/GeneralLuis.cs new file mode 100644 index 0000000000..0ae2cc2c3b --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Services/GeneralLuis.cs @@ -0,0 +1,89 @@ +// +// Code generated by LUISGen +// Tool github: https://github.com/microsoft/botbuilder-tools +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. +// +using Newtonsoft.Json; +using System.Collections.Generic; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.AI.Luis; +namespace Luis +{ + public class GeneralLuis : IRecognizerConvert + { + public string Text; + public string AlteredText; + public enum Intent + { + Cancel, + Confirm, + Escalate, + FinishTask, + GoBack, + Help, + Logout, + None, + ReadAloud, + Reject, + Repeat, + SelectAny, + SelectItem, + SelectNone, + ShowNext, + ShowPrevious, + StartOver, + Stop + }; + public Dictionary Intents; + + public class _Entities + { + // Simple entities + public string[] DirectionalReference; + + // Built-in entities + public double[] number; + public double[] ordinal; + + // Instance + public class _Instance + { + public InstanceData[] DirectionalReference; + public InstanceData[] number; + public InstanceData[] ordinal; + } + [JsonProperty("$instance")] + public _Instance _instance; + } + public _Entities Entities; + + [JsonExtensionData(ReadData = true, WriteData = true)] + public IDictionary Properties { get; set; } + + public void Convert(dynamic result) + { + var app = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(result)); + Text = app.Text; + AlteredText = app.AlteredText; + Intents = app.Intents; + Entities = app.Entities; + Properties = app.Properties; + } + + public (Intent intent, double score) TopIntent() + { + Intent maxIntent = Intent.None; + var max = 0.0; + foreach (var entry in Intents) + { + if (entry.Value.Score > max) + { + maxIntent = entry.Key; + max = entry.Value.Score.Value; + } + } + return (maxIntent, max); + } + } +} diff --git a/skills/src/csharp/experimental/eventskill/Startup.cs b/skills/src/csharp/experimental/eventskill/Startup.cs new file mode 100644 index 0000000000..44997761e6 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Startup.cs @@ -0,0 +1,127 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Linq; +using EventSkill.Adapters; +using EventSkill.Bots; +using EventSkill.Dialogs; +using EventSkill.Responses.Main; +using EventSkill.Responses.Shared; +using EventSkill.Services; +using Microsoft.ApplicationInsights; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.ApplicationInsights; +using Microsoft.Bot.Builder.Azure; +using Microsoft.Bot.Builder.BotFramework; +using Microsoft.Bot.Builder.Integration.ApplicationInsights.Core; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Builder.Skills; +using Microsoft.Bot.Builder.Solutions; +using Microsoft.Bot.Builder.Solutions.Responses; +using Microsoft.Bot.Builder.Solutions.TaskExtensions; +using Microsoft.Bot.Connector.Authentication; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace EventSkill +{ + public class Startup + { + public Startup(IHostingEnvironment env, ILoggerFactory loggerFactory) + { + var builder = new ConfigurationBuilder() + .SetBasePath(env.ContentRootPath) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) + .AddJsonFile("cognitivemodels.json", optional: true) + .AddJsonFile($"cognitivemodels.{env.EnvironmentName}.json", optional: true) + .AddJsonFile("skills.json", optional: true) + .AddJsonFile($"skills.{env.EnvironmentName}.json", optional: true) + .AddEnvironmentVariables(); + + Configuration = builder.Build(); + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); + + var provider = services.BuildServiceProvider(); + + // Load settings + var settings = new BotSettings(); + Configuration.Bind(settings); + services.AddSingleton(settings); + services.AddSingleton(settings); + + // Configure credentials + services.AddSingleton(); + services.AddSingleton(new MicrosoftAppCredentials(settings.MicrosoftAppId, settings.MicrosoftAppPassword)); + + // Configure telemetry + services.AddApplicationInsightsTelemetry(); + var telemetryClient = new BotTelemetryClient(new TelemetryClient()); + services.AddSingleton(telemetryClient); + services.AddBotApplicationInsights(telemetryClient); + + // Configure bot services + services.AddSingleton(); + + // Configure storage + // Uncomment the following line for local development without Cosmos Db + // services.AddSingleton(); + services.AddSingleton(new CosmosDbStorage(settings.CosmosDb)); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(sp => + { + var userState = sp.GetService(); + var conversationState = sp.GetService(); + return new BotStateSet(userState, conversationState); + }); + + // Configure proactive + services.AddSingleton(); + services.AddHostedService(); + + // Configure responses + services.AddSingleton(sp => new ResponseManager( + settings.CognitiveModels.Select(l => l.Key).ToArray(), + new MainResponses(), + new SharedResponses())); + + // Register dialogs + services.AddTransient(); + + // Configure adapters + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + + // Configure bot + services.AddTransient>(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseBotApplicationInsights() + .UseDefaultFiles() + .UseStaticFiles() + .UseWebSockets() + .UseMvc(); + } + } +} \ No newline at end of file diff --git a/skills/src/csharp/experimental/eventskill/appsettings.json b/skills/src/csharp/experimental/eventskill/appsettings.json new file mode 100644 index 0000000000..d8f3e4bcb8 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/appsettings.json @@ -0,0 +1,19 @@ +{ + "microsoftAppId": "", + "microsoftAppPassword": "", + "oauthConnections": [], + "ApplicationInsights": { + "InstrumentationKey": "" + }, + "blobStorage": { + "connectionString": "", + "container": "transcripts" + }, + "cosmosDb": { + "authkey": "", + "cosmosDBEndpoint": "", + "collectionId": "botstate-collection", + "databaseId": "botstate-db" + }, + "properties": {} +} diff --git a/skills/src/csharp/experimental/eventskill/cognitivemodels.json b/skills/src/csharp/experimental/eventskill/cognitivemodels.json new file mode 100644 index 0000000000..2199d5f57a --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/cognitivemodels.json @@ -0,0 +1,29 @@ +{ + "cognitiveModels": { + "en": { + "languageModels": [ + { + "id": "General", + "name": "", + "appid": "", + "version": "0.1", + "region": "", + "authoringkey": "", + "authoringRegion": "", + "subscriptionkey": "" + }, + { + "id": "Event", + "name": "", + "appid": "", + "version": "0.1", + "region": "", + "authoringkey": "", + "authoringRegion": "", + "subscriptionkey": "" + } + ] + } + }, + "defaultLocale": "en-us" +} diff --git a/skills/src/csharp/experimental/eventskill/manifestTemplate.json b/skills/src/csharp/experimental/eventskill/manifestTemplate.json new file mode 100644 index 0000000000..178de6855e --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/manifestTemplate.json @@ -0,0 +1,26 @@ +{ + "id": "eventSkill", + "name": "EventSkill", + "description": "The Event experimental skill uses the Eventbrite API to retrieve local event information.", + "iconUrl": "", + "authenticationConnections": [], + "actions": [ + { + "id": "EventSkill_getEvents", + "definition": { + "description": "Finds events happening in the area today.", + "slots": [], + "triggers": { + "utteranceSources": [ + { + "locale": "en", + "source": [ + "EventSkill#GetEvents" + ] + } + ] + } + } + } + ] +} \ No newline at end of file diff --git a/skills/src/csharp/experimental/eventskill/readme.md b/skills/src/csharp/experimental/eventskill/readme.md new file mode 100644 index 0000000000..0afaeb3437 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/readme.md @@ -0,0 +1 @@ +Please refer to the [Skill Template documentation](http://aka.ms/virtualassistantdocs) for deployment and customization instructions. diff --git a/skills/src/csharp/experimental/eventskill/wwwroot/default.htm b/skills/src/csharp/experimental/eventskill/wwwroot/default.htm new file mode 100644 index 0000000000..17a9350195 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/wwwroot/default.htm @@ -0,0 +1,426 @@ + + + + + + + Event Skill + + + + + +
+
+
+
Event Skill
+
+
+
+
+
Your Skill is ready!
+
+ You can test your Skill in the Bot Framework Emulator
+ by opening the Emulator and providing the Endpoint shown below along with the Microsoft AppId and Password which you can find in appsettings.json.
+
+ +
+ Your Skill's endpoint URL typically looks + like this: +
+
https://your_bots_hostname/api/messages
+
+
In addition the Skill manifest endpoint can be found here: +
+
https://your_bots_hostname/api/skill/manifest
+
+
+
+
+ +
+ + + \ No newline at end of file From ae543aa72087f4745aaa6a4068a335442716cf96 Mon Sep 17 00:00:00 2001 From: litofish Date: Mon, 19 Aug 2019 14:34:43 -0700 Subject: [PATCH 16/23] remove other languages --- .../experimental/eventskill/EventSkill.csproj | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/skills/src/csharp/experimental/eventskill/EventSkill.csproj b/skills/src/csharp/experimental/eventskill/EventSkill.csproj index 5dbe7dc7cd..7feee3f95d 100644 --- a/skills/src/csharp/experimental/eventskill/EventSkill.csproj +++ b/skills/src/csharp/experimental/eventskill/EventSkill.csproj @@ -86,11 +86,6 @@
- - True - True - general.lu - True True @@ -101,26 +96,6 @@ True skill.lu - - True - True - general.lu - - - True - True - skill.lu - - - True - True - general.lu - - - True - True - skill.lu - True True From 4f9ad920157a18cccc718f5fafe91ecc39d8b6b4 Mon Sep 17 00:00:00 2001 From: litofish Date: Mon, 19 Aug 2019 15:03:02 -0700 Subject: [PATCH 17/23] Add files for basic find events dialog --- .../Deployment/Resources/LU/en/Event.lu | 2 +- ...{SkillDialogBase.cs => EventDialogBase.cs} | 6 +-- .../eventskill/Dialogs/FindEventsDialog.cs | 26 +++++++++++ .../eventskill/Dialogs/MainDialog.cs | 10 ++++- .../experimental/eventskill/EventSkill.csproj | 11 +++++ .../eventskill/Properties/launchSettings.json | 2 +- .../FindEvents/FindEventsResponses.cs | 17 ++++++++ .../FindEvents/FindEventsResponses.json | 11 +++++ .../FindEvents/FindEventsResponses.tt | 3 ++ .../Responses/Main/MainResponses.json | 4 +- .../eventskill/Services/EventLuis.cs | 11 +++-- .../eventskill/Services/GeneralLuis.cs | 43 +++++++++---------- .../csharp/experimental/eventskill/Startup.cs | 5 ++- 13 files changed, 114 insertions(+), 37 deletions(-) rename skills/src/csharp/experimental/eventskill/Dialogs/{SkillDialogBase.cs => EventDialogBase.cs} (98%) create mode 100644 skills/src/csharp/experimental/eventskill/Dialogs/FindEventsDialog.cs create mode 100644 skills/src/csharp/experimental/eventskill/Responses/FindEvents/FindEventsResponses.cs create mode 100644 skills/src/csharp/experimental/eventskill/Responses/FindEvents/FindEventsResponses.json create mode 100644 skills/src/csharp/experimental/eventskill/Responses/FindEvents/FindEventsResponses.tt diff --git a/skills/src/csharp/experimental/eventskill/Deployment/Resources/LU/en/Event.lu b/skills/src/csharp/experimental/eventskill/Deployment/Resources/LU/en/Event.lu index 3eb952794b..13601b4d61 100644 --- a/skills/src/csharp/experimental/eventskill/Deployment/Resources/LU/en/Event.lu +++ b/skills/src/csharp/experimental/eventskill/Deployment/Resources/LU/en/Event.lu @@ -1,4 +1,4 @@ -# GetEvents +# FindEvents - find events near me - get local events - what's happening nearby diff --git a/skills/src/csharp/experimental/eventskill/Dialogs/SkillDialogBase.cs b/skills/src/csharp/experimental/eventskill/Dialogs/EventDialogBase.cs similarity index 98% rename from skills/src/csharp/experimental/eventskill/Dialogs/SkillDialogBase.cs rename to skills/src/csharp/experimental/eventskill/Dialogs/EventDialogBase.cs index e8010354c4..79dffc0e34 100644 --- a/skills/src/csharp/experimental/eventskill/Dialogs/SkillDialogBase.cs +++ b/skills/src/csharp/experimental/eventskill/Dialogs/EventDialogBase.cs @@ -20,9 +20,9 @@ namespace EventSkill.Dialogs { - public class SkillDialogBase : ComponentDialog + public class EventDialogBase : ComponentDialog { - public SkillDialogBase( + public EventDialogBase( string dialogId, BotSettings settings, BotServices services, @@ -146,7 +146,7 @@ protected async Task GetLuisResult(DialogContext dc) // Get luis service for current locale var locale = CultureInfo.CurrentUICulture.TwoLetterISOLanguageName; var localeConfig = Services.CognitiveModelSets[locale]; - var luisService = localeConfig.LuisServices["EventSkill"]; + var luisService = localeConfig.LuisServices["Event"]; // Get intent and entities for activity var result = await luisService.RecognizeAsync(dc.Context, CancellationToken.None); diff --git a/skills/src/csharp/experimental/eventskill/Dialogs/FindEventsDialog.cs b/skills/src/csharp/experimental/eventskill/Dialogs/FindEventsDialog.cs new file mode 100644 index 0000000000..cf514eca9f --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Dialogs/FindEventsDialog.cs @@ -0,0 +1,26 @@ +using EventSkill.Services; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Builder.Solutions.Responses; + +namespace EventSkill.Dialogs +{ + public class FindEventsDialog : EventDialogBase + { + public FindEventsDialog( + BotSettings settings, + BotServices services, + ResponseManager responseManager, + ConversationState conversationState, + IBotTelemetryClient telemetryClient) + : base(nameof(FindEventsDialog), settings, services, responseManager, conversationState, telemetryClient) + { + var findEvents = new WaterfallStep[] + { + + }; + + AddDialog(new WaterfallDialog(nameof(FindEventsDialog), findEvents)); + } + } +} diff --git a/skills/src/csharp/experimental/eventskill/Dialogs/MainDialog.cs b/skills/src/csharp/experimental/eventskill/Dialogs/MainDialog.cs index e086eff611..1af71a1b99 100644 --- a/skills/src/csharp/experimental/eventskill/Dialogs/MainDialog.cs +++ b/skills/src/csharp/experimental/eventskill/Dialogs/MainDialog.cs @@ -36,6 +36,7 @@ public MainDialog( ResponseManager responseManager, UserState userState, ConversationState conversationState, + FindEventsDialog findEventsDialog, IBotTelemetryClient telemetryClient) : base(nameof(MainDialog), telemetryClient) { @@ -49,6 +50,7 @@ public MainDialog( _contextAccessor = userState.CreateProperty(nameof(SkillContext)); // Register dialogs + AddDialog(findEventsDialog ?? throw new ArgumentNullException(nameof(findEventsDialog))); } protected override async Task OnStartAsync(DialogContext dc, CancellationToken cancellationToken = default(CancellationToken)) @@ -67,7 +69,7 @@ public MainDialog( await PopulateStateFromSemanticAction(dc.Context); // Get skill LUIS model from configuration - localeConfig.LuisServices.TryGetValue("EventSkill", out var luisService); + localeConfig.LuisServices.TryGetValue("Event", out var luisService); if (luisService == null) { @@ -81,6 +83,12 @@ public MainDialog( switch (intent) { + case EventLuis.Intent.FindEvents: + { + // searching for local events + turnResult = await dc.BeginDialogAsync(nameof(FindEventsDialog)); + break; + } case EventLuis.Intent.None: { diff --git a/skills/src/csharp/experimental/eventskill/EventSkill.csproj b/skills/src/csharp/experimental/eventskill/EventSkill.csproj index 7feee3f95d..966da2dead 100644 --- a/skills/src/csharp/experimental/eventskill/EventSkill.csproj +++ b/skills/src/csharp/experimental/eventskill/EventSkill.csproj @@ -6,6 +6,7 @@ + @@ -21,6 +22,7 @@ + @@ -71,6 +73,10 @@ + + TextTemplatingFileGenerator + FindEventsResponses.cs + TextTemplatingFileGenerator MainResponses.cs @@ -116,6 +122,11 @@ True readme.md + + True + True + FindEventsResponses.tt + True True diff --git a/skills/src/csharp/experimental/eventskill/Properties/launchSettings.json b/skills/src/csharp/experimental/eventskill/Properties/launchSettings.json index 913e8a92a4..c156a2bafb 100644 --- a/skills/src/csharp/experimental/eventskill/Properties/launchSettings.json +++ b/skills/src/csharp/experimental/eventskill/Properties/launchSettings.json @@ -3,7 +3,7 @@ "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { - "applicationUrl": "http://localhost:3978/", + "applicationUrl": "http://localhost:4000/", "sslPort": 0 } }, diff --git a/skills/src/csharp/experimental/eventskill/Responses/FindEvents/FindEventsResponses.cs b/skills/src/csharp/experimental/eventskill/Responses/FindEvents/FindEventsResponses.cs new file mode 100644 index 0000000000..9e7731a82e --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Responses/FindEvents/FindEventsResponses.cs @@ -0,0 +1,17 @@ +// https://docs.microsoft.com/en-us/visualstudio/modeling/t4-include-directive?view=vs-2017 +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Bot.Builder.Solutions.Responses; + +namespace EventSkill.Responses.FindEvents +{ + /// + /// Contains bot responses. + /// + public class FindEventsResponses : IResponseIdCollection + { + // Generated accessors + public const string LocationPrompt = "LocationPrompt"; + } +} \ No newline at end of file diff --git a/skills/src/csharp/experimental/eventskill/Responses/FindEvents/FindEventsResponses.json b/skills/src/csharp/experimental/eventskill/Responses/FindEvents/FindEventsResponses.json new file mode 100644 index 0000000000..92aa05768e --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Responses/FindEvents/FindEventsResponses.json @@ -0,0 +1,11 @@ +{ + "LocationPrompt": { + "replies": [ + { + "text": "Where are you?", + "speak": "Where are you?" + } + ], + "inputHint": "acceptingInput" + } +} diff --git a/skills/src/csharp/experimental/eventskill/Responses/FindEvents/FindEventsResponses.tt b/skills/src/csharp/experimental/eventskill/Responses/FindEvents/FindEventsResponses.tt new file mode 100644 index 0000000000..f204f0981b --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Responses/FindEvents/FindEventsResponses.tt @@ -0,0 +1,3 @@ +<#@ template debug="false" hostspecific="true" language="C#" #> +<#@ output extension=".cs" #> +<#@ include file="..\Shared\ResponseIdCollection.t4"#> \ No newline at end of file diff --git a/skills/src/csharp/experimental/eventskill/Responses/Main/MainResponses.json b/skills/src/csharp/experimental/eventskill/Responses/Main/MainResponses.json index 2cb741b57f..3570193c69 100644 --- a/skills/src/csharp/experimental/eventskill/Responses/Main/MainResponses.json +++ b/skills/src/csharp/experimental/eventskill/Responses/Main/MainResponses.json @@ -2,8 +2,8 @@ "WelcomeMessage": { "replies": [ { - "text": "[Enter your intro message here]", - "speak": "[Enter your intro message here]" + "text": "Welcome to the Event Skill!", + "speak": "Welcome to the Event Skill!" } ], "suggestedActions": [], diff --git a/skills/src/csharp/experimental/eventskill/Services/EventLuis.cs b/skills/src/csharp/experimental/eventskill/Services/EventLuis.cs index 063c28ffad..92fb28c136 100644 --- a/skills/src/csharp/experimental/eventskill/Services/EventLuis.cs +++ b/skills/src/csharp/experimental/eventskill/Services/EventLuis.cs @@ -10,13 +10,12 @@ using Microsoft.Bot.Builder.AI.Luis; namespace Luis { - public class EventLuis : IRecognizerConvert + public partial class EventLuis: IRecognizerConvert { public string Text; public string AlteredText; - public enum Intent - { - GetEvents, + public enum Intent { + FindEvents, None }; public Dictionary Intents; @@ -34,11 +33,11 @@ public class _Instance public _Entities Entities; [JsonExtensionData(ReadData = true, WriteData = true)] - public IDictionary Properties { get; set; } + public IDictionary Properties {get; set; } public void Convert(dynamic result) { - var app = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(result)); + var app = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(result, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore })); Text = app.Text; AlteredText = app.AlteredText; Intents = app.Intents; diff --git a/skills/src/csharp/experimental/eventskill/Services/GeneralLuis.cs b/skills/src/csharp/experimental/eventskill/Services/GeneralLuis.cs index 0ae2cc2c3b..bb0b784046 100644 --- a/skills/src/csharp/experimental/eventskill/Services/GeneralLuis.cs +++ b/skills/src/csharp/experimental/eventskill/Services/GeneralLuis.cs @@ -10,29 +10,28 @@ using Microsoft.Bot.Builder.AI.Luis; namespace Luis { - public class GeneralLuis : IRecognizerConvert + public partial class GeneralLuis: IRecognizerConvert { public string Text; public string AlteredText; - public enum Intent - { - Cancel, - Confirm, - Escalate, - FinishTask, - GoBack, - Help, - Logout, - None, - ReadAloud, - Reject, - Repeat, - SelectAny, - SelectItem, - SelectNone, - ShowNext, - ShowPrevious, - StartOver, + public enum Intent { + Cancel, + Confirm, + Escalate, + FinishTask, + GoBack, + Help, + Logout, + None, + ReadAloud, + Reject, + Repeat, + SelectAny, + SelectItem, + SelectNone, + ShowNext, + ShowPrevious, + StartOver, Stop }; public Dictionary Intents; @@ -59,11 +58,11 @@ public class _Instance public _Entities Entities; [JsonExtensionData(ReadData = true, WriteData = true)] - public IDictionary Properties { get; set; } + public IDictionary Properties {get; set; } public void Convert(dynamic result) { - var app = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(result)); + var app = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(result, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore })); Text = app.Text; AlteredText = app.AlteredText; Intents = app.Intents; diff --git a/skills/src/csharp/experimental/eventskill/Startup.cs b/skills/src/csharp/experimental/eventskill/Startup.cs index 44997761e6..7f25db18e1 100644 --- a/skills/src/csharp/experimental/eventskill/Startup.cs +++ b/skills/src/csharp/experimental/eventskill/Startup.cs @@ -5,6 +5,7 @@ using EventSkill.Adapters; using EventSkill.Bots; using EventSkill.Dialogs; +using EventSkill.Responses.FindEvents; using EventSkill.Responses.Main; using EventSkill.Responses.Shared; using EventSkill.Services; @@ -95,9 +96,11 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(sp => new ResponseManager( settings.CognitiveModels.Select(l => l.Key).ToArray(), new MainResponses(), - new SharedResponses())); + new SharedResponses(), + new FindEventsResponses())); // Register dialogs + services.AddTransient(); services.AddTransient(); // Configure adapters From fb473e5d21d95df48c187c2ff7e71f1387b127c6 Mon Sep 17 00:00:00 2001 From: litofish Date: Mon, 19 Aug 2019 17:32:03 -0700 Subject: [PATCH 18/23] Add Eventbrite service to get event information --- .../eventskill/Dialogs/FindEventsDialog.cs | 34 ++++++++++- .../Models/Eventbrite/AugmentedLocation.cs | 16 ++++++ .../Models/Eventbrite/DateTimeTZ.cs | 17 ++++++ .../eventskill/Models/Eventbrite/Event.cs | 56 +++++++++++++++++++ .../Models/Eventbrite/EventSearchResult.cs | 17 ++++++ .../eventskill/Models/Eventbrite/Location.cs | 22 ++++++++ .../eventskill/Models/Eventbrite/Logo.cs | 16 ++++++ .../Models/Eventbrite/MultipartText.cs | 17 ++++++ .../Models/Eventbrite/OriginalLogo.cs | 16 ++++++ .../Models/Eventbrite/Pagination.cs | 22 ++++++++ .../eventskill/Services/BotSettings.cs | 1 + .../eventskill/Services/EventbriteService.cs | 33 +++++++++++ .../experimental/eventskill/appsettings.json | 3 +- 13 files changed, 267 insertions(+), 3 deletions(-) create mode 100644 skills/src/csharp/experimental/eventskill/Models/Eventbrite/AugmentedLocation.cs create mode 100644 skills/src/csharp/experimental/eventskill/Models/Eventbrite/DateTimeTZ.cs create mode 100644 skills/src/csharp/experimental/eventskill/Models/Eventbrite/Event.cs create mode 100644 skills/src/csharp/experimental/eventskill/Models/Eventbrite/EventSearchResult.cs create mode 100644 skills/src/csharp/experimental/eventskill/Models/Eventbrite/Location.cs create mode 100644 skills/src/csharp/experimental/eventskill/Models/Eventbrite/Logo.cs create mode 100644 skills/src/csharp/experimental/eventskill/Models/Eventbrite/MultipartText.cs create mode 100644 skills/src/csharp/experimental/eventskill/Models/Eventbrite/OriginalLogo.cs create mode 100644 skills/src/csharp/experimental/eventskill/Models/Eventbrite/Pagination.cs create mode 100644 skills/src/csharp/experimental/eventskill/Services/EventbriteService.cs diff --git a/skills/src/csharp/experimental/eventskill/Dialogs/FindEventsDialog.cs b/skills/src/csharp/experimental/eventskill/Dialogs/FindEventsDialog.cs index cf514eca9f..a0701c680a 100644 --- a/skills/src/csharp/experimental/eventskill/Dialogs/FindEventsDialog.cs +++ b/skills/src/csharp/experimental/eventskill/Dialogs/FindEventsDialog.cs @@ -1,4 +1,8 @@ -using EventSkill.Services; +using System; +using System.Threading; +using System.Threading.Tasks; +using EventSkill.Responses.FindEvents; +using EventSkill.Services; using Microsoft.Bot.Builder; using Microsoft.Bot.Builder.Dialogs; using Microsoft.Bot.Builder.Solutions.Responses; @@ -7,6 +11,8 @@ namespace EventSkill.Dialogs { public class FindEventsDialog : EventDialogBase { + private EventbriteService _eventbriteService; + public FindEventsDialog( BotSettings settings, BotServices services, @@ -17,10 +23,34 @@ public FindEventsDialog( { var findEvents = new WaterfallStep[] { - + GetLocation, + FindEvents }; + _eventbriteService = new EventbriteService(settings); + AddDialog(new WaterfallDialog(nameof(FindEventsDialog), findEvents)); + AddDialog(new TextPrompt(DialogIds.LocationPrompt)); + } + + private async Task GetLocation(WaterfallStepContext sc, CancellationToken cancellationToken) + { + return await sc.PromptAsync(DialogIds.LocationPrompt, new PromptOptions() + { + Prompt = ResponseManager.GetResponse(FindEventsResponses.LocationPrompt) + }); + } + + private async Task FindEvents(WaterfallStepContext sc, CancellationToken cancellationToken) + { + var location = (string)sc.Result; + var events = await _eventbriteService.GetEventsAsync(location); + return await sc.EndDialogAsync(); + } + + private class DialogIds + { + public const string LocationPrompt = "locationPrompt"; } } } diff --git a/skills/src/csharp/experimental/eventskill/Models/Eventbrite/AugmentedLocation.cs b/skills/src/csharp/experimental/eventskill/Models/Eventbrite/AugmentedLocation.cs new file mode 100644 index 0000000000..914118d3f3 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Models/Eventbrite/AugmentedLocation.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace EventSkill.Models +{ + public class AugmentedLocation + { + [JsonProperty("city")] + public string City { get; set; } + + [JsonProperty("region")] + public string Region { get; set; } + + [JsonProperty("country")] + public string Country { get; set; } + } +} diff --git a/skills/src/csharp/experimental/eventskill/Models/Eventbrite/DateTimeTZ.cs b/skills/src/csharp/experimental/eventskill/Models/Eventbrite/DateTimeTZ.cs new file mode 100644 index 0000000000..dcb44ca27b --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Models/Eventbrite/DateTimeTZ.cs @@ -0,0 +1,17 @@ +using System; +using Newtonsoft.Json; + +namespace EventSkill.Models.Eventbrite +{ + public class DateTimeTZ + { + [JsonProperty("timezone")] + public string Timezone { get; set; } + + [JsonProperty("local")] + public DateTime Local { get; set; } + + [JsonProperty("utc")] + public DateTime Utc { get; set; } + } +} diff --git a/skills/src/csharp/experimental/eventskill/Models/Eventbrite/Event.cs b/skills/src/csharp/experimental/eventskill/Models/Eventbrite/Event.cs new file mode 100644 index 0000000000..30c43f1740 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Models/Eventbrite/Event.cs @@ -0,0 +1,56 @@ +using System; +using Newtonsoft.Json; + +namespace EventSkill.Models.Eventbrite +{ + public class Event + { + [JsonProperty("name")] + public MultipartText Name { get; set; } + + [JsonProperty("description")] + public MultipartText Description { get; set; } + + [JsonProperty("summary")] + public string Summary { get; set; } + + [JsonProperty("id")] + public string Id { get; set; } + + [JsonProperty("url")] + public string Url { get; set; } + + [JsonProperty("start")] + public DateTimeTZ Start { get; set; } + + [JsonProperty("end")] + public DateTimeTZ End { get; set; } + + [JsonProperty("created")] + public DateTime Created { get; set; } + + [JsonProperty("changed")] + public DateTime Changed { get; set; } + + [JsonProperty("published")] + public DateTime Published { get; set; } + + [JsonProperty("status")] + public string Status { get; set; } + + [JsonProperty("currency")] + public string Currency { get; set; } + + [JsonProperty("online_event")] + public bool OnlineEvent { get; set; } + + [JsonProperty("hide_start_date")] + public bool HideStartDate { get; set; } + + [JsonProperty("hide_end_date")] + public bool HideEndDate { get; set; } + + [JsonProperty("logo")] + public Logo Logo { get; set; } + } +} diff --git a/skills/src/csharp/experimental/eventskill/Models/Eventbrite/EventSearchResult.cs b/skills/src/csharp/experimental/eventskill/Models/Eventbrite/EventSearchResult.cs new file mode 100644 index 0000000000..e86e545f54 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Models/Eventbrite/EventSearchResult.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace EventSkill.Models.Eventbrite +{ + public class EventSearchResult + { + [JsonProperty("pagination")] + public Pagination Pagination { get; set; } + + [JsonProperty("events")] + public List Events { get; set; } + + [JsonProperty("location")] + public Location Location { get; set; } + } +} diff --git a/skills/src/csharp/experimental/eventskill/Models/Eventbrite/Location.cs b/skills/src/csharp/experimental/eventskill/Models/Eventbrite/Location.cs new file mode 100644 index 0000000000..cbad835442 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Models/Eventbrite/Location.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; + +namespace EventSkill.Models +{ + public class Location + { + [JsonProperty("latitude")] + public string Latitude { get; set; } + + [JsonProperty("augmented_location")] + public AugmentedLocation AugmentedLocation { get; set; } + + [JsonProperty("within")] + public string Within { get; set; } + + [JsonProperty("longitude")] + public string Longitude { get; set; } + + [JsonProperty("address")] + public string Address { get; set; } + } +} diff --git a/skills/src/csharp/experimental/eventskill/Models/Eventbrite/Logo.cs b/skills/src/csharp/experimental/eventskill/Models/Eventbrite/Logo.cs new file mode 100644 index 0000000000..a4ef368050 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Models/Eventbrite/Logo.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace EventSkill.Models.Eventbrite +{ + public class Logo + { + [JsonProperty("original")] + public OriginalLogo Original { get; set; } + + [JsonProperty("id")] + public string Id { get; set; } + + [JsonProperty("url")] + public string Url { get; set; } + } +} diff --git a/skills/src/csharp/experimental/eventskill/Models/Eventbrite/MultipartText.cs b/skills/src/csharp/experimental/eventskill/Models/Eventbrite/MultipartText.cs new file mode 100644 index 0000000000..c780a34d48 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Models/Eventbrite/MultipartText.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace EventSkill.Models.Eventbrite +{ + public class MultipartText + { + [JsonProperty("text")] + public string Text { get; set; } + + [JsonProperty("html")] + public string Html { get; set; } + } +} diff --git a/skills/src/csharp/experimental/eventskill/Models/Eventbrite/OriginalLogo.cs b/skills/src/csharp/experimental/eventskill/Models/Eventbrite/OriginalLogo.cs new file mode 100644 index 0000000000..ebe7615969 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Models/Eventbrite/OriginalLogo.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace EventSkill.Models.Eventbrite +{ + public class OriginalLogo + { + [JsonProperty("url")] + public string Url { get; set; } + + [JsonProperty("width")] + public int Width { get; set; } + + [JsonProperty("height")] + public int Height { get; set; } + } +} diff --git a/skills/src/csharp/experimental/eventskill/Models/Eventbrite/Pagination.cs b/skills/src/csharp/experimental/eventskill/Models/Eventbrite/Pagination.cs new file mode 100644 index 0000000000..bbe326a7de --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Models/Eventbrite/Pagination.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; + +namespace EventSkill.Models.Eventbrite +{ + public class Pagination + { + [JsonProperty("object_count")] + public int ObjectCount { get; set; } + + [JsonProperty("page_number")] + public int PageNumber { get; set; } + + [JsonProperty("page_size")] + public int PageSize { get; set; } + + [JsonProperty("page_count")] + public int PageCount { get; set; } + + [JsonProperty("has_more_items")] + public bool HasMoreItems { get; set; } + } +} diff --git a/skills/src/csharp/experimental/eventskill/Services/BotSettings.cs b/skills/src/csharp/experimental/eventskill/Services/BotSettings.cs index f520ce7124..fbc7b9db47 100644 --- a/skills/src/csharp/experimental/eventskill/Services/BotSettings.cs +++ b/skills/src/csharp/experimental/eventskill/Services/BotSettings.cs @@ -7,5 +7,6 @@ namespace EventSkill.Services { public class BotSettings : BotSettingsBase { + public string EventbriteApiKey { get; set; } } } \ No newline at end of file diff --git a/skills/src/csharp/experimental/eventskill/Services/EventbriteService.cs b/skills/src/csharp/experimental/eventskill/Services/EventbriteService.cs new file mode 100644 index 0000000000..79325a3035 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Services/EventbriteService.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; +using EventSkill.Models.Eventbrite; +using Newtonsoft.Json; + +namespace EventSkill.Services +{ + public sealed class EventbriteService + { + private const string LocationAndDateApiUrl = "https://www.eventbriteapi.com/v3/events/search/?location.address={0}&location.within={1}&start_date.keyword={2}&token={3}"; + private static string _apiKey; + private static HttpClient _httpClient; + + public EventbriteService(BotSettings settings) + { + _apiKey = settings.EventbriteApiKey ?? throw new Exception("The EventbriteKey must be provided to use this dialog."); + _httpClient = new HttpClient(); + _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + } + + public async Task> GetEventsAsync(string location) + { + var url = string.Format(LocationAndDateApiUrl, location, "10mi", "today", _apiKey); + var response = await _httpClient.GetStringAsync(url); + var apiResponse = JsonConvert.DeserializeObject(response); + + return apiResponse.Events; + } + } +} diff --git a/skills/src/csharp/experimental/eventskill/appsettings.json b/skills/src/csharp/experimental/eventskill/appsettings.json index d8f3e4bcb8..ea22216b80 100644 --- a/skills/src/csharp/experimental/eventskill/appsettings.json +++ b/skills/src/csharp/experimental/eventskill/appsettings.json @@ -15,5 +15,6 @@ "collectionId": "botstate-collection", "databaseId": "botstate-db" }, - "properties": {} + "properties": {}, + "eventbriteKey": "" } From fa6a439d9165cfd64be0beff50cecb47a8886353 Mon Sep 17 00:00:00 2001 From: litofish Date: Wed, 21 Aug 2019 14:43:06 -0700 Subject: [PATCH 19/23] Update Luis model and create adaptive card for events --- .../eventskill/Content/EventCard.json | 96 +++++++++++++++++++ .../Deployment/Resources/LU/en/Event.lu | 52 ++++++++-- .../Deployment/Resources/LU/en/General.lu | 21 +++- .../eventskill/Dialogs/EventDialogBase.cs | 4 + .../eventskill/Dialogs/FindEventsDialog.cs | 81 ++++++++++++++-- .../eventskill/Dialogs/MainDialog.cs | 2 - .../experimental/eventskill/EventSkill.csproj | 2 + .../eventskill/Models/EventCardData.cs | 19 ++++ .../eventskill/Models/EventSkillUserState.cs | 14 +++ .../eventskill/Models/Eventbrite/Address.cs | 28 ++++++ .../eventskill/Models/Eventbrite/Event.cs | 31 ++---- .../eventskill/Models/Eventbrite/Logo.cs | 3 - .../Models/Eventbrite/MultipartText.cs | 6 +- .../Models/Eventbrite/OriginalLogo.cs | 16 ---- .../Models/Eventbrite/TicketAvailability.cs | 25 +++++ .../Models/Eventbrite/TicketPrice.cs | 16 ++++ .../eventskill/Models/Eventbrite/Venue.cs | 16 ++++ .../FindEvents/FindEventsResponses.cs | 2 + .../FindEvents/FindEventsResponses.json | 20 +++- .../eventskill/Services/EventbriteService.cs | 7 +- 20 files changed, 392 insertions(+), 69 deletions(-) create mode 100644 skills/src/csharp/experimental/eventskill/Content/EventCard.json create mode 100644 skills/src/csharp/experimental/eventskill/Models/EventCardData.cs create mode 100644 skills/src/csharp/experimental/eventskill/Models/EventSkillUserState.cs create mode 100644 skills/src/csharp/experimental/eventskill/Models/Eventbrite/Address.cs delete mode 100644 skills/src/csharp/experimental/eventskill/Models/Eventbrite/OriginalLogo.cs create mode 100644 skills/src/csharp/experimental/eventskill/Models/Eventbrite/TicketAvailability.cs create mode 100644 skills/src/csharp/experimental/eventskill/Models/Eventbrite/TicketPrice.cs create mode 100644 skills/src/csharp/experimental/eventskill/Models/Eventbrite/Venue.cs diff --git a/skills/src/csharp/experimental/eventskill/Content/EventCard.json b/skills/src/csharp/experimental/eventskill/Content/EventCard.json new file mode 100644 index 0000000000..19fc2f7563 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Content/EventCard.json @@ -0,0 +1,96 @@ +{ + "type": "AdaptiveCard", + "id": "EventCard", + "body": [ + { + "type": "Container", + "backgroundImage": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAmwAAADVCAIAAABlrTvmAAAACXBIWXMAABYlAAAWJQFJUiTwAAAG0mlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDUgNzkuMTYzNDk5LCAyMDE4LzA4LzEzLTE2OjQwOjIyICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoTWFjaW50b3NoKSIgeG1wOkNyZWF0ZURhdGU9IjIwMTktMDMtMjJUMTc6MDM6NDAtMDc6MDAiIHhtcDpNb2RpZnlEYXRlPSIyMDE5LTAzLTIyVDE3OjE2OjU2LTA3OjAwIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDE5LTAzLTIyVDE3OjE2OjU2LTA3OjAwIiBkYzpmb3JtYXQ9ImltYWdlL3BuZyIgcGhvdG9zaG9wOkNvbG9yTW9kZT0iMyIgcGhvdG9zaG9wOklDQ1Byb2ZpbGU9InNSR0IgSUVDNjE5NjYtMi4xIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjY0ZGY4YjdiLTM2MTYtNDRkNy04MTI3LTgyNzk4NmUyODk3ZSIgeG1wTU06RG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOmExNDU0NmE3LWZhODMtMjc0Mi1hNWU0LWIxMzYzNjQ2NWU5NyIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOjhlMDlmYmE5LTE2ZjktNGZiNC05MzdhLThkOTYzMGMxOTQyNiI+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249ImNyZWF0ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6OGUwOWZiYTktMTZmOS00ZmI0LTkzN2EtOGQ5NjMwYzE5NDI2IiBzdEV2dDp3aGVuPSIyMDE5LTAzLTIyVDE3OjAzOjQwLTA3OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoTWFjaW50b3NoKSIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6Yzc5ODg2ZTQtZTg3Zi00MmMzLWI5ZjYtN2FkMDg3YTlkOTg4IiBzdEV2dDp3aGVuPSIyMDE5LTAzLTIyVDE3OjE2OjU2LTA3OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoTWFjaW50b3NoKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6NjRkZjhiN2ItMzYxNi00NGQ3LTgxMjctODI3OTg2ZTI4OTdlIiBzdEV2dDp3aGVuPSIyMDE5LTAzLTIyVDE3OjE2OjU2LTA3OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoTWFjaW50b3NoKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7bJaRFAAAIbUlEQVR4nO3dy27bSAKGUZKWFCfuLKYH6N712/XzzTvNE8ykJzfHkmZRhpN2ZItkFVkXnrOUNj9AgB9IkVD/57/+3QHQuk9fvv33w6fcK6Z5ux/2N/3Fry5/uroh9wAAFtdYQcshogCNU9DliChAyxR0USIK0CwFXZqIArRJQVcgogANUtB1iChAaxR0NSIK0BQFXZOIArRDQVcmogCNUND1iShACxQ0CxEFqJ6C5iKiAHVT0IxEFKBiCpqXiALUSkGzE1GAKtVY0Nu2CtqJKECNKi3ooa2CdiIKUB0FLYeIAtREQYsiogDVUNDSiChAHRS0QCIKUAEFLZOIApROQYslogBFU9CSiShAuRS0cCIKUCgFLZ+IApTo/ttDjQXdDxsqaCeiAAU6nc//+fA594ppNljQTkQBCvTx0/3xeMq9YoJtFrQTUYACff5yn3vCBJstaCeiAKX59nCs6DJ0ywXtRBSgNF++PuSeMNbGC9qJKEBparkMVdBORAFKczxVEFEFDUQUgGkU9ImIAjCBgv5IRAHKctjvck94kYI+I6IAZdnvCj0zK+jPCj1UAJt1OJR4JaqgF4koQFmGvn97e8i94m8U9CUiClCc93dvck/4TkFfIaIAxbm5GX65u829ousU9BoRBSjR+7s3u91N3g0KepWIAhTqn/+4y9hRBR1DRAEKNfR9ro4q6EgiClCuLB1V0PFEFKBoK3dUQScRUYDSrdbR2/2wU9ApRBSgAit0VEFnEFGAOizaUQWdR0QBqrFQRxV0NhEFqEnyjnqSKIaIAlQmYUcVNJKIAtQnSUcVNJ6IAlQpsqMKmoSIAtRqdkcVNBURBajYjI4qaEIiClC3SR1V0LREFKB6IzvqfdDkRBSgBVc7qqBLEFGARrzSUQVdiIgCtONiRxV0OSIK0JRnHVXQRYkoQGueOqqgSxNRgAYNff/H7+/fHXa5hzRORAEadHcYbnfDb7/eHZb8H29EFKA1d4fhcNN3XTcMvY4uSkQBmvJU0EBHFyWiAO14VtBAR5cjogCNuFjQQEcXIqIALXi3f7GggY4uQUQBqvduP7zZXX8fVEeTE1GAuo0saKCjaYkoQMUmFTTQ0YREFKBWMwoa6GgqIgpQpdkFDXQ0CREFqE9kQQMdjSeiAJVJUtBARyOJKEBNrr4POpWOxhBRgGokL2igo7OJKEAdFipooKPziChABRYtaKCjM4goQOlWKGigo1OJKEDRVitooKOTiChAuVYuaKCj44koQKGyFDTQ0ZFEFKBEGQsa6OgYIgpQnOwFDXT0KhEFKEshBQ109HUiClCQogoa6OgrRBSgFAUWNNDRl4goQBGKLWigoxeJKEB+hRc00NGfiShAZlUUNNDRZ0QUIKeKChro6I9EFCCb6goa6OgTEQXIo9KCBjoaiChABlUXNNDRTkQB1tdAQQMdFVGAVTVT0GDjHRVRgPU0VtBgyx0VUYCVNFnQYLMdFVGANTRc0GCbHRVRgMU1X9Bggx0VUYBlbaSgwdY6KqIAC9pUQYNNdVREAZaywYIG2+moiAIsYrMFDYah/+3XX5rvqIgCpLfxggZb6KiIAiR2u+sVNGi+oyIKkNJu6G93Tq3ftd1RRxogpbd759XnGu6ogw2QzOGmdx/3olY7KqIAyewl9GVNdlREAdLo+24/iOhr2uuoiAKkcdMr6HWNdVREAdJwK3ekljoqogBpuBAdr5mOiigAGbTRUREFSON4zr2gNg10VEQB0jiJ6HS1d1REAdI4ns6n3BtqVHVHRRQgmQe3dGept6MiCpDMVxGdq9KOiihAMsfT+V5H56qxoyIKkNLnh5NfRmerrqMiCpDS+dx9vNfR+erqqIgCJHY8nXU0RkUdFVGA9HQ0Ui0dFVGARehopCo6KqIAS3nsqMd15yq/oyIKsKDj6fzxm47OV3hHRRRgWToaqeSOiijA4nQ0UrEdFVGANYSOyuhsZXZURAFWcjyd/3evo/MV2FERBViPjkYqraMiCrAqHY0UOrovo6MiCrA2HY00DP3vZXRURAEy0NFIhXRURAHy0NFIJXRURAGy0dFI2TsqogA56WikvB0VUYDMdDRSxo6KKEB+OhopV0dFFKAIOhopS0dFFKAUOhpp/Y6KKEBBdDTSyh0VUYCy6GikNTsqogDF0dFIq3VURAFKpKOR1umoiAIUKnQ094qKrdBREQUo1/F0/ktHIyzdUREFKJqORlq0oyIKUDodjfTY0X36joooQAV0NNJCHRVRgDroaKQlOiqiANXQ0UjJOyqiADUJHT17gXSutB0VUYDK6GikhB0VUYD66GikVB0VUYAq6Wik0NFDXEdFFKBWOhopvqMiClAxHY0U2VERBaibjkaK6aiIAlRPRyPNfs5IRAFaoKOR5nVURAEaoaORZnRURAHaoaORpv4+KqIATTmezn991dH5JnVURAFaczzraJTxHRVRgAbpaKSRHRVRgDbpaKQxHRVRgGbpaKSrHRVRgJbpaKTXOyqiAI3T0UivdFREAdqno5Fe6qiIAmyCjka62FERBdiK0NGTjs71c0dFFGBDdDTSs46KKMC26GikHzsqogCbo6ORnjoqogBbpKORQkdFFGCjdDTSMPQiCrBdOhpJRAE2zfujMUQUYOt0dDYRBUBHZxJRALpOR2cRUQAePXY094yKiCgA3+noJCIKwN88nHR0LBEF4DkdHUlEAbhAR8cQUQAu09GrRBSAF+no60QUgNfo6CtEFIArdPQlIgrAdTp6kYgCMIqO/kxEARhLR58RUQAm0NEfiSgA0+joExEFYDIdDUQUgDl0tBNRAGbTUREFYL6Nd1REAYiy5Y6KKACxNttREQUggW12VEQBSOOxo1sKqYgCkMzD6fxhSx39P2CJeSCLm9hfAAAAAElFTkSuQmCC", + "items": [ + { + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "items": [ + { + "type": "Image", + "id": "icon", + "horizontalAlignment": "Center", + "url": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTAiIHZpZXdCb3g9IjAgMCAxNiAxMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE2IDRDMTUuODU5NCA0IDE1LjcyOTIgNC4wMjYwNCAxNS42MDk0IDQuMDc4MTJDMTUuNDg5NiA0LjEzMDIxIDE1LjM4MjggNC4yMDMxMiAxNS4yODkxIDQuMjk2ODhDMTUuMjAwNSA0LjM4NTQyIDE1LjEzMDIgNC40ODk1OCAxNS4wNzgxIDQuNjA5MzhDMTUuMDI2IDQuNzI5MTcgMTUgNC44NTkzOCAxNSA1QzE1IDUuMTQwNjIgMTUuMDI2IDUuMjcwODMgMTUuMDc4MSA1LjM5MDYyQzE1LjEzMDIgNS41MTA0MiAxNS4yMDA1IDUuNjE3MTkgMTUuMjg5MSA1LjcxMDk0QzE1LjM4MjggNS43OTk0OCAxNS40ODk2IDUuODY5NzkgMTUuNjA5NCA1LjkyMTg4QzE1LjcyOTIgNS45NzM5NiAxNS44NTk0IDYgMTYgNlYxMEgwVjZDMC4xNDA2MjUgNiAwLjI3MDgzMyA1Ljk3Mzk2IDAuMzkwNjI1IDUuOTIxODhDMC41MTA0MTcgNS44Njk3OSAwLjYxNDU4MyA1Ljc5OTQ4IDAuNzAzMTI1IDUuNzEwOTRDMC43OTY4NzUgNS42MTcxOSAwLjg2OTc5MiA1LjUxMDQyIDAuOTIxODc1IDUuMzkwNjJDMC45NzM5NTggNS4yNzA4MyAxIDUuMTQwNjIgMSA1QzEgNC44NTkzOCAwLjk3Mzk1OCA0LjcyOTE3IDAuOTIxODc1IDQuNjA5MzhDMC44Njk3OTIgNC40ODk1OCAwLjc5Njg3NSA0LjM4NTQyIDAuNzAzMTI1IDQuMjk2ODhDMC42MTQ1ODMgNC4yMDMxMiAwLjUxMDQxNyA0LjEzMDIxIDAuMzkwNjI1IDQuMDc4MTJDMC4yNzA4MzMgNC4wMjYwNCAwLjE0MDYyNSA0IDAgNFYwSDE2VjRaTTE1IDFIMVYzLjI2NTYyQzEuMzEyNSAzLjQ0NzkyIDEuNTU3MjkgMy42OTI3MSAxLjczNDM4IDRDMS45MTE0NiA0LjMwNzI5IDIgNC42NDA2MiAyIDVDMiA1LjM1OTM4IDEuOTExNDYgNS42OTI3MSAxLjczNDM4IDZDMS41NTcyOSA2LjMwNzI5IDEuMzEyNSA2LjU1MjA4IDEgNi43MzQzOFY5SDE1VjYuNzM0MzhDMTQuNjg3NSA2LjU1MjA4IDE0LjQ0MjcgNi4zMDcyOSAxNC4yNjU2IDZDMTQuMDg4NSA1LjY5MjcxIDE0IDUuMzU5MzggMTQgNUMxNCA0LjY0MDYyIDE0LjA4ODUgNC4zMDcyOSAxNC4yNjU2IDRDMTQuNDQyNyAzLjY5MjcxIDE0LjY4NzUgMy40NDc5MiAxNSAzLjI2NTYyVjFaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K", + "size": "Small", + "width": "30px", + "height": "30px" + } + ], + "width": "auto" + }, + { + "type": "Column", + "verticalContentAlignment": "Center", + "items": [ + { + "type": "TextBlock", + "id": "title", + "size": "Large", + "weight": "Bolder", + "color": "Light", + "text": "Local Event" + } + ], + "width": "stretch" + } + ] + } + ] + }, + { + "type": "Image", + "url": "{ImageUrl}" + }, + { + "type": "TextBlock", + "text": "**{Title}**", + "size": "Medium", + "weight": "Bolder" + }, + { + "type": "Container", + "items": [ + { + "type": "TextBlock", + "text": "{StartDate}", + "isSubtle": true, + "spacing": "Small" + }, + { + "type": "TextBlock", + "text": "{Location}", + "isSubtle": true, + "spacing": "Small" + }, + { + "type": "TextBlock", + "text": "{Price}", + "isSubtle": true, + "spacing": "Small" + } + ] + }, + { + "type": "TextBlock", + "text": "Powered by **Eventbrite**", + "horizontalAlignment": "Right", + "size": "Small" + } + ], + "actions": [ + { + "type": "Action.OpenUrl", + "title": "Get Tickets", + "url": "{Url}" + } + ], + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "version": "1.0", + "speak": "{Speak}" +} \ No newline at end of file diff --git a/skills/src/csharp/experimental/eventskill/Deployment/Resources/LU/en/Event.lu b/skills/src/csharp/experimental/eventskill/Deployment/Resources/LU/en/Event.lu index 13601b4d61..19f216cf9d 100644 --- a/skills/src/csharp/experimental/eventskill/Deployment/Resources/LU/en/Event.lu +++ b/skills/src/csharp/experimental/eventskill/Deployment/Resources/LU/en/Event.lu @@ -1,13 +1,51 @@ -# FindEvents +> ! Automatically generated by [LUDown CLI](https://github.com/Microsoft/botbuilder-tools/tree/master/Ludown), Wed Aug 21 2019 14:37:19 GMT-0700 (Pacific Daylight Time) + +> ! Source LUIS JSON file: stdin + +> ! Source QnA TSV file: Not Specified + +> ! Source QnA Alterations file: Not Specified + + +> # Intent definitions + +## FindEvents +- are there any events near me +- can you recommend events in the area +- can you recommend things for me to do +- find an event in my area - find events near me +- find events nearby +- find me something to do +- find me something to do nearby - get local events +- i want to go to an event nearby +- recommend events in my area +- recommend events nearby +- recommend things to do nearby +- what events are happening nearby +- whats happening in my area - what's happening nearby -- are there any events near me -- find me something to do -- can you recommend events in the area -# None -- hi + +## None +- goodbye - hello +- hi - logout -- goodbye \ No newline at end of file + + +> # Entity definitions + + +> # PREBUILT Entity definitions + + +> # Phrase list definitions + + +> # List entities + +> # RegEx entities + + diff --git a/skills/src/csharp/experimental/eventskill/Deployment/Resources/LU/en/General.lu b/skills/src/csharp/experimental/eventskill/Deployment/Resources/LU/en/General.lu index 7ede276df9..bdf02ee0a9 100644 --- a/skills/src/csharp/experimental/eventskill/Deployment/Resources/LU/en/General.lu +++ b/skills/src/csharp/experimental/eventskill/Deployment/Resources/LU/en/General.lu @@ -1,3 +1,12 @@ +> ! Automatically generated by [LUDown CLI](https://github.com/Microsoft/botbuilder-tools/tree/master/Ludown), Wed Aug 21 2019 14:37:29 GMT-0700 (Pacific Daylight Time) + +> ! Source LUIS JSON file: stdin + +> ! Source QnA TSV file: Not Specified + +> ! Source QnA Alterations file: Not Specified + + > # Intent definitions ## Cancel @@ -158,12 +167,14 @@ - some help - who can help me + ## Logout -- signout - forget me -- sign out -- logout - log out +- logout +- sign out +- signout + ## None - all of them @@ -519,3 +530,7 @@ $PREBUILT:ordinal > # List entities + +> # RegEx entities + + diff --git a/skills/src/csharp/experimental/eventskill/Dialogs/EventDialogBase.cs b/skills/src/csharp/experimental/eventskill/Dialogs/EventDialogBase.cs index 79dffc0e34..d753227d7d 100644 --- a/skills/src/csharp/experimental/eventskill/Dialogs/EventDialogBase.cs +++ b/skills/src/csharp/experimental/eventskill/Dialogs/EventDialogBase.cs @@ -28,12 +28,14 @@ public EventDialogBase( BotServices services, ResponseManager responseManager, ConversationState conversationState, + UserState userState, IBotTelemetryClient telemetryClient) : base(dialogId) { Services = services; ResponseManager = responseManager; StateAccessor = conversationState.CreateProperty(nameof(EventSkillState)); + UserAccessor = userState.CreateProperty(nameof(EventSkillUserState)); TelemetryClient = telemetryClient; // NOTE: Uncomment the following if your skill requires authentication @@ -51,6 +53,8 @@ public EventDialogBase( protected IStatePropertyAccessor StateAccessor { get; set; } + protected IStatePropertyAccessor UserAccessor { get; set; } + protected ResponseManager ResponseManager { get; set; } protected override async Task OnBeginDialogAsync(DialogContext dc, object options, CancellationToken cancellationToken = default(CancellationToken)) diff --git a/skills/src/csharp/experimental/eventskill/Dialogs/FindEventsDialog.cs b/skills/src/csharp/experimental/eventskill/Dialogs/FindEventsDialog.cs index a0701c680a..f575d01f98 100644 --- a/skills/src/csharp/experimental/eventskill/Dialogs/FindEventsDialog.cs +++ b/skills/src/csharp/experimental/eventskill/Dialogs/FindEventsDialog.cs @@ -1,6 +1,9 @@ using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using EventSkill.Models; +using EventSkill.Models.Eventbrite; using EventSkill.Responses.FindEvents; using EventSkill.Services; using Microsoft.Bot.Builder; @@ -18,8 +21,9 @@ public FindEventsDialog( BotServices services, ResponseManager responseManager, ConversationState conversationState, + UserState userState, IBotTelemetryClient telemetryClient) - : base(nameof(FindEventsDialog), settings, services, responseManager, conversationState, telemetryClient) + : base(nameof(FindEventsDialog), settings, services, responseManager, conversationState, userState, telemetryClient) { var findEvents = new WaterfallStep[] { @@ -30,24 +34,87 @@ public FindEventsDialog( _eventbriteService = new EventbriteService(settings); AddDialog(new WaterfallDialog(nameof(FindEventsDialog), findEvents)); - AddDialog(new TextPrompt(DialogIds.LocationPrompt)); + AddDialog(new TextPrompt(DialogIds.LocationPrompt, ValidateLocationPrompt)); } private async Task GetLocation(WaterfallStepContext sc, CancellationToken cancellationToken) { - return await sc.PromptAsync(DialogIds.LocationPrompt, new PromptOptions() + var userState = await UserAccessor.GetAsync(sc.Context, () => new EventSkillUserState()); + + if (string.IsNullOrWhiteSpace(userState.Location)) + { + return await sc.PromptAsync(DialogIds.LocationPrompt, new PromptOptions() + { + Prompt = ResponseManager.GetResponse(FindEventsResponses.LocationPrompt), + RetryPrompt = ResponseManager.GetResponse(FindEventsResponses.RetryLocationPrompt) + }); + } + + return await sc.NextAsync(); + } + + private async Task ValidateLocationPrompt(PromptValidatorContext promptContext, CancellationToken cancellationToken) + { + var userState = await UserAccessor.GetAsync(promptContext.Context, () => new EventSkillUserState()); + if (promptContext.Recognized.Succeeded && !string.IsNullOrWhiteSpace(promptContext.Recognized.Value)) { - Prompt = ResponseManager.GetResponse(FindEventsResponses.LocationPrompt) - }); + userState.Location = promptContext.Recognized.Value; + return true; + } + + return false; } private async Task FindEvents(WaterfallStepContext sc, CancellationToken cancellationToken) { - var location = (string)sc.Result; - var events = await _eventbriteService.GetEventsAsync(location); + var userState = await UserAccessor.GetAsync(sc.Context, () => new EventSkillUserState()); + + var location = userState.Location; + List events = await _eventbriteService.GetEventsAsync(location); + List cards = new List(); + + foreach (var item in events) + { + var eventCardData = new EventCardData() + { + Title = item.Name.Text, + ImageUrl = item?.Logo?.Url, + StartDate = item.Start.Local.ToString("dddd, MMMM dd, h:mm tt"), + Location = GetVenueLocation(item), + Price = item.IsFree ? "Free" : "Starts at " + + Convert.ToDouble(item.TicketAvailability.MinTicketPrice.MajorValue) + .ToString("C", System.Globalization.CultureInfo.GetCultureInfo(item.Locale)), + Url = item.Url + }; + + cards.Add(new Card("EventCard", eventCardData)); + } + + await sc.Context.SendActivityAsync(ResponseManager.GetCardResponse(FindEventsResponses.FoundEvents, cards, null)); + return await sc.EndDialogAsync(); } + // Get formatted location string based on data event has + private string GetVenueLocation(Event eventData) + { + string venueLocation = null; + if (string.IsNullOrEmpty(eventData.Venue?.Address?.LocalizedAreaDisplay)) + { + venueLocation = eventData.Venue.Name; + } + else if (string.IsNullOrEmpty(eventData.Venue?.Name)) + { + venueLocation = eventData.Venue.Address.LocalizedAreaDisplay; + } + else + { + venueLocation = string.Format("{0}, {1}", eventData.Venue.Name, eventData.Venue.Address.LocalizedAreaDisplay); + } + + return venueLocation; + } + private class DialogIds { public const string LocationPrompt = "locationPrompt"; diff --git a/skills/src/csharp/experimental/eventskill/Dialogs/MainDialog.cs b/skills/src/csharp/experimental/eventskill/Dialogs/MainDialog.cs index 1af71a1b99..8c9ea5b619 100644 --- a/skills/src/csharp/experimental/eventskill/Dialogs/MainDialog.cs +++ b/skills/src/csharp/experimental/eventskill/Dialogs/MainDialog.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System; -using System.Collections.Generic; using System.Globalization; using System.Threading; using System.Threading.Tasks; @@ -14,7 +13,6 @@ using Microsoft.Bot.Builder; using Microsoft.Bot.Builder.Dialogs; using Microsoft.Bot.Builder.Skills; -using Microsoft.Bot.Builder.Skills.Models; using Microsoft.Bot.Builder.Solutions; using Microsoft.Bot.Builder.Solutions.Dialogs; using Microsoft.Bot.Builder.Solutions.Responses; diff --git a/skills/src/csharp/experimental/eventskill/EventSkill.csproj b/skills/src/csharp/experimental/eventskill/EventSkill.csproj index 966da2dead..493b2adb5c 100644 --- a/skills/src/csharp/experimental/eventskill/EventSkill.csproj +++ b/skills/src/csharp/experimental/eventskill/EventSkill.csproj @@ -6,6 +6,7 @@ + @@ -22,6 +23,7 @@ + diff --git a/skills/src/csharp/experimental/eventskill/Models/EventCardData.cs b/skills/src/csharp/experimental/eventskill/Models/EventCardData.cs new file mode 100644 index 0000000000..2c72421cbe --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Models/EventCardData.cs @@ -0,0 +1,19 @@ +using Microsoft.Bot.Builder.Solutions.Responses; + +namespace EventSkill.Models +{ + public class EventCardData : ICardData + { + public string Title { get; set; } + + public string ImageUrl { get; set; } + + public string StartDate { get; set; } + + public string Location { get; set; } + + public string Price { get; set; } + + public string Url { get; set; } + } +} diff --git a/skills/src/csharp/experimental/eventskill/Models/EventSkillUserState.cs b/skills/src/csharp/experimental/eventskill/Models/EventSkillUserState.cs new file mode 100644 index 0000000000..6695b68795 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Models/EventSkillUserState.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace EventSkill.Models +{ + public class EventSkillUserState + { + public string Location { get; set; } + + public void Clear() + { + } + } +} diff --git a/skills/src/csharp/experimental/eventskill/Models/Eventbrite/Address.cs b/skills/src/csharp/experimental/eventskill/Models/Eventbrite/Address.cs new file mode 100644 index 0000000000..9d9fdfb575 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Models/Eventbrite/Address.cs @@ -0,0 +1,28 @@ +using Newtonsoft.Json; + +namespace EventSkill.Models.Eventbrite +{ + public class Address + { + [JsonProperty("address_1")] + public string Address1 { get; set; } + + [JsonProperty("city")] + public string City { get; set; } + + [JsonProperty("region")] + public string Region { get; set; } + + [JsonProperty("postal_code")] + public string PostalCode { get; set; } + + [JsonProperty("country")] + public string Country { get; set; } + + [JsonProperty("localized_address_display")] + public string LocalizedAddressDisplay { get; set; } + + [JsonProperty("localized_area_display")] + public string LocalizedAreaDisplay { get; set; } + } +} diff --git a/skills/src/csharp/experimental/eventskill/Models/Eventbrite/Event.cs b/skills/src/csharp/experimental/eventskill/Models/Eventbrite/Event.cs index 30c43f1740..61167e0888 100644 --- a/skills/src/csharp/experimental/eventskill/Models/Eventbrite/Event.cs +++ b/skills/src/csharp/experimental/eventskill/Models/Eventbrite/Event.cs @@ -1,5 +1,4 @@ -using System; -using Newtonsoft.Json; +using Newtonsoft.Json; namespace EventSkill.Models.Eventbrite { @@ -26,29 +25,17 @@ public class Event [JsonProperty("end")] public DateTimeTZ End { get; set; } - [JsonProperty("created")] - public DateTime Created { get; set; } + [JsonProperty("locale")] + public string Locale { get; set; } - [JsonProperty("changed")] - public DateTime Changed { get; set; } + [JsonProperty("is_free")] + public bool IsFree { get; set; } - [JsonProperty("published")] - public DateTime Published { get; set; } + [JsonProperty("venue")] + public Venue Venue { get; set; } - [JsonProperty("status")] - public string Status { get; set; } - - [JsonProperty("currency")] - public string Currency { get; set; } - - [JsonProperty("online_event")] - public bool OnlineEvent { get; set; } - - [JsonProperty("hide_start_date")] - public bool HideStartDate { get; set; } - - [JsonProperty("hide_end_date")] - public bool HideEndDate { get; set; } + [JsonProperty("ticket_availability")] + public TicketAvailability TicketAvailability { get; set; } [JsonProperty("logo")] public Logo Logo { get; set; } diff --git a/skills/src/csharp/experimental/eventskill/Models/Eventbrite/Logo.cs b/skills/src/csharp/experimental/eventskill/Models/Eventbrite/Logo.cs index a4ef368050..a2e14f958e 100644 --- a/skills/src/csharp/experimental/eventskill/Models/Eventbrite/Logo.cs +++ b/skills/src/csharp/experimental/eventskill/Models/Eventbrite/Logo.cs @@ -4,9 +4,6 @@ namespace EventSkill.Models.Eventbrite { public class Logo { - [JsonProperty("original")] - public OriginalLogo Original { get; set; } - [JsonProperty("id")] public string Id { get; set; } diff --git a/skills/src/csharp/experimental/eventskill/Models/Eventbrite/MultipartText.cs b/skills/src/csharp/experimental/eventskill/Models/Eventbrite/MultipartText.cs index c780a34d48..9bef0e4005 100644 --- a/skills/src/csharp/experimental/eventskill/Models/Eventbrite/MultipartText.cs +++ b/skills/src/csharp/experimental/eventskill/Models/Eventbrite/MultipartText.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Newtonsoft.Json; +using Newtonsoft.Json; namespace EventSkill.Models.Eventbrite { diff --git a/skills/src/csharp/experimental/eventskill/Models/Eventbrite/OriginalLogo.cs b/skills/src/csharp/experimental/eventskill/Models/Eventbrite/OriginalLogo.cs deleted file mode 100644 index ebe7615969..0000000000 --- a/skills/src/csharp/experimental/eventskill/Models/Eventbrite/OriginalLogo.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Newtonsoft.Json; - -namespace EventSkill.Models.Eventbrite -{ - public class OriginalLogo - { - [JsonProperty("url")] - public string Url { get; set; } - - [JsonProperty("width")] - public int Width { get; set; } - - [JsonProperty("height")] - public int Height { get; set; } - } -} diff --git a/skills/src/csharp/experimental/eventskill/Models/Eventbrite/TicketAvailability.cs b/skills/src/csharp/experimental/eventskill/Models/Eventbrite/TicketAvailability.cs new file mode 100644 index 0000000000..580d8949c2 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Models/Eventbrite/TicketAvailability.cs @@ -0,0 +1,25 @@ +using Newtonsoft.Json; + +namespace EventSkill.Models.Eventbrite +{ + public class TicketAvailability + { + [JsonProperty("has_available_tickets")] + public bool HasAvailableTickets { get; set; } + + [JsonProperty("minimum_ticket_price")] + public TicketPrice MinTicketPrice { get; set; } + + [JsonProperty("maximum_ticket_price")] + public TicketPrice MaxTicketPrice { get; set; } + + [JsonProperty("is_sold_out")] + public bool IsSoldOut { get; set; } + + [JsonProperty("start_sales_date")] + public DateTimeTZ StartSalesDate { get; set; } + + [JsonProperty("waitlist_available")] + public bool WaitlistAvailable { get; set; } + } +} diff --git a/skills/src/csharp/experimental/eventskill/Models/Eventbrite/TicketPrice.cs b/skills/src/csharp/experimental/eventskill/Models/Eventbrite/TicketPrice.cs new file mode 100644 index 0000000000..d94baa1af6 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Models/Eventbrite/TicketPrice.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace EventSkill.Models.Eventbrite +{ + public class TicketPrice + { + [JsonProperty("currency")] + public string Currency { get; set; } + + [JsonProperty("major_value")] + public string MajorValue { get; set; } + + [JsonProperty("display")] + public string Display { get; set; } + } +} diff --git a/skills/src/csharp/experimental/eventskill/Models/Eventbrite/Venue.cs b/skills/src/csharp/experimental/eventskill/Models/Eventbrite/Venue.cs new file mode 100644 index 0000000000..46f58dea33 --- /dev/null +++ b/skills/src/csharp/experimental/eventskill/Models/Eventbrite/Venue.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace EventSkill.Models.Eventbrite +{ + public class Venue + { + [JsonProperty("address")] + public Address Address { get; set; } + + [JsonProperty("id")] + public string Id { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + } +} diff --git a/skills/src/csharp/experimental/eventskill/Responses/FindEvents/FindEventsResponses.cs b/skills/src/csharp/experimental/eventskill/Responses/FindEvents/FindEventsResponses.cs index 9e7731a82e..4dcf021ee8 100644 --- a/skills/src/csharp/experimental/eventskill/Responses/FindEvents/FindEventsResponses.cs +++ b/skills/src/csharp/experimental/eventskill/Responses/FindEvents/FindEventsResponses.cs @@ -13,5 +13,7 @@ public class FindEventsResponses : IResponseIdCollection { // Generated accessors public const string LocationPrompt = "LocationPrompt"; + public const string RetryLocationPrompt = "RetryLocationPrompt"; + public const string FoundEvents = "FoundEvents"; } } \ No newline at end of file diff --git a/skills/src/csharp/experimental/eventskill/Responses/FindEvents/FindEventsResponses.json b/skills/src/csharp/experimental/eventskill/Responses/FindEvents/FindEventsResponses.json index 92aa05768e..156a489488 100644 --- a/skills/src/csharp/experimental/eventskill/Responses/FindEvents/FindEventsResponses.json +++ b/skills/src/csharp/experimental/eventskill/Responses/FindEvents/FindEventsResponses.json @@ -6,6 +6,24 @@ "speak": "Where are you?" } ], - "inputHint": "acceptingInput" + "inputHint": "expectingInput" + }, + "RetryLocationPrompt": { + "replies": [ + { + "text": "Please provide your location.", + "speak": "Please provide your location." + } + ], + "inputHint": "expectingInput" + }, + "FoundEvents": { + "replies": [ + { + "text": "Here's what I found happening this week:", + "speak": "Here's what I found happening this week:" + } + ], + "inputHint": "ignoringInput" } } diff --git a/skills/src/csharp/experimental/eventskill/Services/EventbriteService.cs b/skills/src/csharp/experimental/eventskill/Services/EventbriteService.cs index 79325a3035..c8d2503836 100644 --- a/skills/src/csharp/experimental/eventskill/Services/EventbriteService.cs +++ b/skills/src/csharp/experimental/eventskill/Services/EventbriteService.cs @@ -10,7 +10,7 @@ namespace EventSkill.Services { public sealed class EventbriteService { - private const string LocationAndDateApiUrl = "https://www.eventbriteapi.com/v3/events/search/?location.address={0}&location.within={1}&start_date.keyword={2}&token={3}"; + private const string LocationAndDateApiUrl = "https://www.eventbriteapi.com/v3/events/search/?location.address={0}&location.within={1}&start_date.keyword={2}&expand=venue,ticket_availability&token={3}"; private static string _apiKey; private static HttpClient _httpClient; @@ -23,11 +23,12 @@ public EventbriteService(BotSettings settings) public async Task> GetEventsAsync(string location) { - var url = string.Format(LocationAndDateApiUrl, location, "10mi", "today", _apiKey); + var url = string.Format(LocationAndDateApiUrl, location, "10mi", "this_week", _apiKey); var response = await _httpClient.GetStringAsync(url); var apiResponse = JsonConvert.DeserializeObject(response); - return apiResponse.Events; + // limit number of events returned + return apiResponse.Events.GetRange(0, 10); } } } From a27d13b29f387f726c89ed6420ae9cc478177566 Mon Sep 17 00:00:00 2001 From: litofish Date: Wed, 21 Aug 2019 15:33:51 -0700 Subject: [PATCH 20/23] Implement populating location state from semantic action --- .../eventskill/Dialogs/FindEventsDialog.cs | 16 ++++++++++++---- .../eventskill/Dialogs/MainDialog.cs | 14 +++++++------- .../eventskill/Models/EventSkillState.cs | 5 +++++ .../eventskill/Models/EventSkillUserState.cs | 1 + .../eventskill/manifestTemplate.json | 9 ++++++++- 5 files changed, 33 insertions(+), 12 deletions(-) diff --git a/skills/src/csharp/experimental/eventskill/Dialogs/FindEventsDialog.cs b/skills/src/csharp/experimental/eventskill/Dialogs/FindEventsDialog.cs index f575d01f98..d2227e8159 100644 --- a/skills/src/csharp/experimental/eventskill/Dialogs/FindEventsDialog.cs +++ b/skills/src/csharp/experimental/eventskill/Dialogs/FindEventsDialog.cs @@ -39,15 +39,23 @@ public FindEventsDialog( private async Task GetLocation(WaterfallStepContext sc, CancellationToken cancellationToken) { + var convState = await StateAccessor.GetAsync(sc.Context, () => new EventSkillState()); var userState = await UserAccessor.GetAsync(sc.Context, () => new EventSkillUserState()); if (string.IsNullOrWhiteSpace(userState.Location)) { - return await sc.PromptAsync(DialogIds.LocationPrompt, new PromptOptions() + if (!string.IsNullOrWhiteSpace(convState.CurrentCoordinates)) { - Prompt = ResponseManager.GetResponse(FindEventsResponses.LocationPrompt), - RetryPrompt = ResponseManager.GetResponse(FindEventsResponses.RetryLocationPrompt) - }); + userState.Location = convState.CurrentCoordinates; + } + else + { + return await sc.PromptAsync(DialogIds.LocationPrompt, new PromptOptions() + { + Prompt = ResponseManager.GetResponse(FindEventsResponses.LocationPrompt), + RetryPrompt = ResponseManager.GetResponse(FindEventsResponses.RetryLocationPrompt) + }); + } } return await sc.NextAsync(); diff --git a/skills/src/csharp/experimental/eventskill/Dialogs/MainDialog.cs b/skills/src/csharp/experimental/eventskill/Dialogs/MainDialog.cs index 8c9ea5b619..7c715969ab 100644 --- a/skills/src/csharp/experimental/eventskill/Dialogs/MainDialog.cs +++ b/skills/src/csharp/experimental/eventskill/Dialogs/MainDialog.cs @@ -240,13 +240,13 @@ private async Task PopulateStateFromSemanticAction(ITurnContext context) // Example of populating local state with data passed through semanticAction out of Activity var activity = context.Activity; var semanticAction = activity.SemanticAction; - //if (semanticAction != null && semanticAction.Entities.ContainsKey("location")) - //{ - // var location = semanticAction.Entities["location"]; - // var locationObj = location.Properties["location"].ToString(); - // var state = await _stateAccessor.GetAsync(context, () => new SkillState()); - // state.CurrentCoordinates = locationObj; - //} + if (semanticAction != null && semanticAction.Entities.ContainsKey("location")) + { + var location = semanticAction.Entities["location"]; + var locationObj = location.Properties["location"].ToString(); + var state = await _stateAccessor.GetAsync(context, () => new EventSkillState()); + state.CurrentCoordinates = locationObj; + } } } } \ No newline at end of file diff --git a/skills/src/csharp/experimental/eventskill/Models/EventSkillState.cs b/skills/src/csharp/experimental/eventskill/Models/EventSkillState.cs index f4e69b193d..69e903fce3 100644 --- a/skills/src/csharp/experimental/eventskill/Models/EventSkillState.cs +++ b/skills/src/csharp/experimental/eventskill/Models/EventSkillState.cs @@ -11,8 +11,13 @@ public class EventSkillState public EventLuis LuisResult { get; set; } + public string CurrentCoordinates { get; internal set; } + public void Clear() { + Token = null; + LuisResult = null; + CurrentCoordinates = null; } } } diff --git a/skills/src/csharp/experimental/eventskill/Models/EventSkillUserState.cs b/skills/src/csharp/experimental/eventskill/Models/EventSkillUserState.cs index 6695b68795..d26f3aa435 100644 --- a/skills/src/csharp/experimental/eventskill/Models/EventSkillUserState.cs +++ b/skills/src/csharp/experimental/eventskill/Models/EventSkillUserState.cs @@ -9,6 +9,7 @@ public class EventSkillUserState public void Clear() { + Location = null; } } } diff --git a/skills/src/csharp/experimental/eventskill/manifestTemplate.json b/skills/src/csharp/experimental/eventskill/manifestTemplate.json index 178de6855e..b77a1a78ce 100644 --- a/skills/src/csharp/experimental/eventskill/manifestTemplate.json +++ b/skills/src/csharp/experimental/eventskill/manifestTemplate.json @@ -9,7 +9,14 @@ "id": "EventSkill_getEvents", "definition": { "description": "Finds events happening in the area today.", - "slots": [], + "slots": [ + { + "name": "location", + "types": [ + "string" + ] + } + ], "triggers": { "utteranceSources": [ { From 0d726eef835ef31e4892b5e7475500af25242002 Mon Sep 17 00:00:00 2001 From: litofish Date: Wed, 21 Aug 2019 16:40:25 -0700 Subject: [PATCH 21/23] fix bugs --- .../experimental/eventskill/Dialogs/FindEventsDialog.cs | 4 ++-- skills/src/csharp/experimental/eventskill/EventSkill.csproj | 1 + .../src/csharp/experimental/eventskill/manifestTemplate.json | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/skills/src/csharp/experimental/eventskill/Dialogs/FindEventsDialog.cs b/skills/src/csharp/experimental/eventskill/Dialogs/FindEventsDialog.cs index d2227e8159..3f0c8a4ed8 100644 --- a/skills/src/csharp/experimental/eventskill/Dialogs/FindEventsDialog.cs +++ b/skills/src/csharp/experimental/eventskill/Dialogs/FindEventsDialog.cs @@ -86,12 +86,12 @@ private async Task FindEvents(WaterfallStepContext sc, Cancell var eventCardData = new EventCardData() { Title = item.Name.Text, - ImageUrl = item?.Logo?.Url, + ImageUrl = item?.Logo?.Url ?? " ", StartDate = item.Start.Local.ToString("dddd, MMMM dd, h:mm tt"), Location = GetVenueLocation(item), Price = item.IsFree ? "Free" : "Starts at " + Convert.ToDouble(item.TicketAvailability.MinTicketPrice.MajorValue) - .ToString("C", System.Globalization.CultureInfo.GetCultureInfo(item.Locale)), + .ToString("C", System.Globalization.CultureInfo.GetCultureInfo(item.Locale.Replace("_", "-"))), Url = item.Url }; diff --git a/skills/src/csharp/experimental/eventskill/EventSkill.csproj b/skills/src/csharp/experimental/eventskill/EventSkill.csproj index 493b2adb5c..1c26cd793d 100644 --- a/skills/src/csharp/experimental/eventskill/EventSkill.csproj +++ b/skills/src/csharp/experimental/eventskill/EventSkill.csproj @@ -3,6 +3,7 @@ netcoreapp2.2 NU1701 + 12ed3ad7-66e5-4160-b86c-aff9da5cf4b6 diff --git a/skills/src/csharp/experimental/eventskill/manifestTemplate.json b/skills/src/csharp/experimental/eventskill/manifestTemplate.json index b77a1a78ce..51cdb4160c 100644 --- a/skills/src/csharp/experimental/eventskill/manifestTemplate.json +++ b/skills/src/csharp/experimental/eventskill/manifestTemplate.json @@ -1,6 +1,6 @@ { "id": "eventSkill", - "name": "EventSkill", + "name": "Event Skill", "description": "The Event experimental skill uses the Eventbrite API to retrieve local event information.", "iconUrl": "", "authenticationConnections": [], @@ -22,7 +22,7 @@ { "locale": "en", "source": [ - "EventSkill#GetEvents" + "Event#GetEvents" ] } ] From fc081cfdb95c569fef3510121ef8a9991e5187b2 Mon Sep 17 00:00:00 2001 From: litofish <39777667+litofish@users.noreply.github.com> Date: Thu, 22 Aug 2019 11:35:56 -0700 Subject: [PATCH 22/23] Revert visual studio version --- skills/src/csharp/Skills.sln | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skills/src/csharp/Skills.sln b/skills/src/csharp/Skills.sln index 2bd17dddba..74f4ffe7b2 100644 --- a/skills/src/csharp/Skills.sln +++ b/skills/src/csharp/Skills.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.28307.779 +# Visual Studio 16 +VisualStudioVersion = 16.0.29102.190 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Skills", "Skills", "{34280F2E-60EB-4566-9ECD-56F114372037}" EndProject From 6896d38761384620999c2247828249f6759caa6a Mon Sep 17 00:00:00 2001 From: litofish <39777667+litofish@users.noreply.github.com> Date: Thu, 22 Aug 2019 11:37:02 -0700 Subject: [PATCH 23/23] Revert visual studio version --- skills/src/csharp/Skills.sln | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/src/csharp/Skills.sln b/skills/src/csharp/Skills.sln index 74f4ffe7b2..a733c24260 100644 --- a/skills/src/csharp/Skills.sln +++ b/skills/src/csharp/Skills.sln @@ -1,6 +1,6 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 16 +# Visual Studio Version 16 VisualStudioVersion = 16.0.29102.190 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Skills", "Skills", "{34280F2E-60EB-4566-9ECD-56F114372037}"