diff --git a/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills.Tests/Mocks/MockSkillTransport.cs b/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills.Tests/Mocks/MockSkillTransport.cs index 826b1f5352..0fe9722a8e 100644 --- a/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills.Tests/Mocks/MockSkillTransport.cs +++ b/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills.Tests/Mocks/MockSkillTransport.cs @@ -6,34 +6,34 @@ namespace Microsoft.Bot.Builder.Skills.Tests.Mocks { - public class MockSkillTransport : ISkillTransport - { - private Activity _activityForwarded; + public class MockSkillTransport : ISkillTransport + { + private Activity _activityForwarded; public Task CancelRemoteDialogsAsync(SkillManifest skillManifest, IServiceClientCredentials serviceClientCredentials, ITurnContext turnContext) { return Task.CompletedTask; } - public void Disconnect() - { - } + public void Disconnect() + { + } - public Task ForwardToSkillAsync(SkillManifest skillManifest, IServiceClientCredentials serviceClientCredentials, ITurnContext dialogContext, Activity activity, Action tokenRequestHandler = null) + public Task ForwardToSkillAsync(SkillManifest skillManifest, IServiceClientCredentials serviceClientCredentials, ITurnContext dialogContext, Activity activity, Action tokenRequestHandler = null, Action fallbackHandler = null) { _activityForwarded = activity; - return Task.FromResult(true); - } + return Task.FromResult(true); + } - public bool CheckIfSkillInvoked() - { - return _activityForwarded != null; - } + public bool CheckIfSkillInvoked() + { + return _activityForwarded != null; + } - public void VerifyActivityForwardedCorrectly(Action assertion) - { - assertion(_activityForwarded); - } - } + public void VerifyActivityForwardedCorrectly(Action assertion) + { + assertion(_activityForwarded); + } + } } \ No newline at end of file diff --git a/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills.Tests/SkillDialogTest.cs b/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills.Tests/SkillDialogTest.cs index 5d21abdea6..d61f783f66 100644 --- a/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills.Tests/SkillDialogTest.cs +++ b/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills.Tests/SkillDialogTest.cs @@ -7,7 +7,7 @@ namespace Microsoft.Bot.Builder.Skills.Tests internal class SkillDialogTest : SkillDialog { public SkillDialogTest(SkillManifest skillManifest, IServiceClientCredentials serviceClientCredentials, IBotTelemetryClient telemetryClient, UserState userState, ISkillTransport skillTransport = null) - : base(skillManifest, serviceClientCredentials, telemetryClient, userState, null, skillTransport) + : base(skillManifest, serviceClientCredentials, telemetryClient, userState, null, null, skillTransport) { } } diff --git a/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills/ISkillIntentRecognizer.cs b/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills/ISkillIntentRecognizer.cs new file mode 100644 index 0000000000..8e9f01418e --- /dev/null +++ b/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills/ISkillIntentRecognizer.cs @@ -0,0 +1,14 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Builder.Skills.Models.Manifest; + +namespace Microsoft.Bot.Builder.Skills +{ + public interface ISkillIntentRecognizer + { + Func> RecognizeSkillIntentAsync { get; } + + bool ConfirmIntentSwitch { get; } + } +} diff --git a/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills/ISkillTransport.cs b/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills/ISkillTransport.cs index 313a96417c..953a9ac971 100644 --- a/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills/ISkillTransport.cs +++ b/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills/ISkillTransport.cs @@ -8,7 +8,7 @@ namespace Microsoft.Bot.Builder.Skills { public interface ISkillTransport { - Task ForwardToSkillAsync(SkillManifest skillManifest, IServiceClientCredentials serviceClientCredentials, ITurnContext dialogContext, Activity activity, Action tokenRequestHandler = null); + Task ForwardToSkillAsync(SkillManifest skillManifest, IServiceClientCredentials serviceClientCredentials, ITurnContext dialogContext, Activity activity, Action tokenRequestHandler = null, Action fallbackHandler = null); Task CancelRemoteDialogsAsync(SkillManifest skillManifest, IServiceClientCredentials serviceClientCredentials, ITurnContext turnContext); diff --git a/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills/Models/SkillEvents.cs b/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills/Models/SkillEvents.cs index 77e490626d..ee64a1ec7d 100644 --- a/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills/Models/SkillEvents.cs +++ b/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills/Models/SkillEvents.cs @@ -3,5 +3,7 @@ public class SkillEvents { public const string CancelAllSkillDialogsEventName = "skill/cancelallskilldialogs"; + public const string FallbackEventName = "skill/fallbackrequest"; + public const string FallbackHandledEventName = "skill/fallbackhandled"; } } \ No newline at end of file diff --git a/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills/SkillCallingRequestHandler.cs b/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills/SkillCallingRequestHandler.cs index d256f82076..db99c1cfb9 100644 --- a/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills/SkillCallingRequestHandler.cs +++ b/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills/SkillCallingRequestHandler.cs @@ -3,6 +3,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using Microsoft.Bot.Builder.Skills.Models; using Microsoft.Bot.Builder.Skills.Protocol; using Microsoft.Bot.Builder.Solutions; using Microsoft.Bot.Schema; @@ -18,13 +19,20 @@ public class SkillCallingRequestHandler : RequestHandler private readonly ITurnContext _turnContext; private readonly IBotTelemetryClient _botTelemetryClient; private readonly Action _tokenRequestHandler; + private readonly Action _fallbackRequestHandler; private readonly Action _handoffActivityHandler; - public SkillCallingRequestHandler(ITurnContext turnContext, IBotTelemetryClient botTelemetryClient, Action tokenRequestHandler = null, Action handoffActivityHandler = null) + public SkillCallingRequestHandler( + ITurnContext turnContext, + IBotTelemetryClient botTelemetryClient, + Action tokenRequestHandler = null, + Action fallbackRequestHandler = null, + Action handoffActivityHandler = null) { _turnContext = turnContext ?? throw new ArgumentNullException(nameof(turnContext)); _botTelemetryClient = botTelemetryClient; _tokenRequestHandler = tokenRequestHandler; + _fallbackRequestHandler = fallbackRequestHandler; _handoffActivityHandler = handoffActivityHandler; var routes = new RouteTemplate[] @@ -54,6 +62,19 @@ public SkillCallingRequestHandler(ITurnContext turnContext, IBotTelemetryClient throw new ArgumentNullException("TokenRequestHandler", "Skill is requesting for token but there's no handler on the calling side!"); } } + else if (activity.Type == ActivityTypes.Event && activity.Name == SkillEvents.FallbackEventName) + { + if (_fallbackRequestHandler != null) + { + _fallbackRequestHandler(activity); + + return new ResourceResponse(); + } + else + { + throw new ArgumentNullException("FallbackRequestHandler", "Skill is asking for fallback but there is no handler on the calling side!"); + } + } else if (activity.Type == ActivityTypes.EndOfConversation) { if (_handoffActivityHandler != null) diff --git a/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills/SkillDialog.cs b/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills/SkillDialog.cs index db7fe6d0f1..cff5fd42b4 100644 --- a/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills/SkillDialog.cs +++ b/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills/SkillDialog.cs @@ -4,11 +4,16 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Builder.Dialogs.Choices; using Microsoft.Bot.Builder.Skills.Auth; +using Microsoft.Bot.Builder.Skills.Models; using Microsoft.Bot.Builder.Skills.Models.Manifest; using Microsoft.Bot.Builder.Solutions; using Microsoft.Bot.Builder.Solutions.Authentication; +using Microsoft.Bot.Builder.Solutions.Dialogs; +using Microsoft.Bot.Builder.Solutions.Resources; using Microsoft.Bot.Schema; +using Microsoft.Recognizers.Text; using Newtonsoft.Json.Linq; namespace Microsoft.Bot.Builder.Skills @@ -29,23 +34,27 @@ public class SkillDialog : ComponentDialog private Queue _queuedResponses = new Queue(); private object _lockObject = new object(); - /// - /// Initializes a new instance of the class. - /// SkillDialog constructor that accepts the manifest description of a Skill along with TelemetryClient for end to end telemetry. - /// - /// Skill manifest. - /// Service client credentials. - /// Telemetry Client. - /// User State. - /// Auth Dialog. - /// Transport used for skill invocation. - public SkillDialog( - SkillManifest skillManifest, - IServiceClientCredentials serviceClientCredentials, - IBotTelemetryClient telemetryClient, - UserState userState, - MultiProviderAuthDialog authDialog = null, - ISkillTransport skillTransport = null) + private ISkillIntentRecognizer _skillIntentRecognizer; + + /// + /// Initializes a new instance of the class. + /// SkillDialog constructor that accepts the manifest description of a Skill along with TelemetryClient for end to end telemetry. + /// + /// Skill manifest. + /// Service client credentials. + /// Telemetry Client. + /// User State. + /// Auth Dialog. + /// Skill Intent Recognizer. + /// Transport used for skill invocation. + public SkillDialog( + SkillManifest skillManifest, + IServiceClientCredentials serviceClientCredentials, + IBotTelemetryClient telemetryClient, + UserState userState, + MultiProviderAuthDialog authDialog = null, + ISkillIntentRecognizer skillIntentRecognizer = null, + ISkillTransport skillTransport = null) : base(skillManifest.Id) { _skillManifest = skillManifest ?? throw new ArgumentNullException(nameof(SkillManifest)); @@ -53,12 +62,67 @@ public SkillDialog( _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); _userState = userState; _skillTransport = skillTransport ?? new SkillWebSocketTransport(telemetryClient); + _skillIntentRecognizer = skillIntentRecognizer; + + var intentSwitching = new WaterfallStep[] + { + ConfirmIntentSwitch, + FinishIntentSwitch, + }; if (authDialog != null) { _authDialog = authDialog; AddDialog(authDialog); } + + AddDialog(new WaterfallDialog(DialogIds.ConfirmSkillSwitchFlow, intentSwitching)); + AddDialog(new ConfirmPrompt(DialogIds.ConfirmSkillSwitchPrompt)); + } + + public async Task ConfirmIntentSwitch(WaterfallStepContext sc, CancellationToken cancellationToken) + { + if (sc.Options != null && sc.Options is SkillSwitchConfirmOption skillSwitchConfirmOption) + { + var newIntentName = skillSwitchConfirmOption.TargetIntent; + var intentResponse = string.Format(CommonStrings.ConfirmSkillSwitch, newIntentName); + return await sc.PromptAsync(DialogIds.ConfirmSkillSwitchPrompt, new PromptOptions() + { + Prompt = new Activity(type: ActivityTypes.Message, text: intentResponse, speak: intentResponse), + }); + } + + return await sc.NextAsync(); + } + + public async Task FinishIntentSwitch(WaterfallStepContext sc, CancellationToken cancellationToken) + { + if (sc.Options != null && sc.Options is SkillSwitchConfirmOption skillSwitchConfirmOption) + { + // Do skill switching + if (sc.Result is bool result && result) + { + // 1) End remote skill dialog + await _skillTransport.CancelRemoteDialogsAsync(_skillManifest, _serviceClientCredentials, sc.Context); + + // 2) Reset user input + sc.Context.Activity.Text = skillSwitchConfirmOption.UserInputActivity.Text; + sc.Context.Activity.Speak = skillSwitchConfirmOption.UserInputActivity.Speak; + + // 3) End dialog + return await sc.EndDialogAsync(true); + } + + // Cancel skill switching + else + { + var dialogResult = await ForwardToSkillAsync(sc, skillSwitchConfirmOption.FallbackHandledEvent); + return await sc.EndDialogAsync(dialogResult); + } + } + + // We should never go here + return await sc.EndDialogAsync(); } public override async Task EndDialogAsync(ITurnContext turnContext, DialogInstance instance, DialogReason reason, CancellationToken cancellationToken) @@ -181,6 +245,33 @@ public override async Task EndDialogAsync(ITurnContext turnContext, DialogInstan } } + if (innerDc.ActiveDialog?.Id == DialogIds.ConfirmSkillSwitchPrompt) + { + var result = await base.OnContinueDialogAsync(innerDc, cancellationToken); + + if (result.Status != DialogTurnStatus.Complete) + { + return result; + } + else + { + // SkillDialog only truely end when confirm skill switch. + if (result.Result is bool dispatchResult && dispatchResult) + { + // Restart and redispatch + result.Result = new RouterDialogTurnResult(RouterDialogTurnStatus.Restart); + } + + // If confirm dialog is ended without skill switch, means previous activity has been resent and SkillDialog can continue to work + else + { + result.Status = DialogTurnStatus.Waiting; + } + + return result; + } + } + var dialogResult = await ForwardToSkillAsync(innerDc, activity); _skillTransport.Disconnect(); @@ -225,25 +316,61 @@ private async Task ForwardToSkillAsync(DialogContext innerDc, { try { - var endOfConversation = await _skillTransport.ForwardToSkillAsync(_skillManifest, _serviceClientCredentials, innerDc.Context, activity, GetTokenRequestCallback(innerDc)); + var endOfConversation = await _skillTransport.ForwardToSkillAsync(_skillManifest, _serviceClientCredentials, innerDc.Context, activity, GetTokenRequestCallback(innerDc), GetFallbackCallback(innerDc)); - if (endOfConversation) + if (endOfConversation) { await innerDc.Context.SendActivityAsync(new Activity(type: ActivityTypes.Trace, text: $"<--Ending the skill conversation with the {_skillManifest.Name} Skill and handing off to Parent Bot.")); return await innerDc.EndDialogAsync(); } else - { - var dialogResult = new DialogTurnResult(DialogTurnStatus.Waiting); + { + var dialogResult = new DialogTurnResult(DialogTurnStatus.Waiting); - // if there's any response we need to send to the skill queued - // forward to skill and start a new turn - while (_queuedResponses.Count > 0 && dialogResult.Status != DialogTurnStatus.Complete && dialogResult.Status != DialogTurnStatus.Cancelled) - { - dialogResult = await ForwardToSkillAsync(innerDc, _queuedResponses.Dequeue()); - } + // if there's any response we need to send to the skill queued + // forward to skill and start a new turn + while (_queuedResponses.Count > 0 && dialogResult.Status != DialogTurnStatus.Complete && dialogResult.Status != DialogTurnStatus.Cancelled) + { + var lastEvent = _queuedResponses.Dequeue(); + + if (lastEvent.Name == SkillEvents.FallbackEventName) + { + // Set fallback event to fallback handled event + lastEvent.Name = SkillEvents.FallbackHandledEventName; + + // if skillIntentRecognizer specified, run the recognizer + if (_skillIntentRecognizer != null && _skillIntentRecognizer.RecognizeSkillIntentAsync != null) + { + var recognizedSkillManifestRecognized = await _skillIntentRecognizer.RecognizeSkillIntentAsync(innerDc); + + // if the result is an actual intent other than the current skill, launch the confirm dialog (if configured) to eventually switch to a different skill + // if the result is the same as the current intent, re-send it to the current skill + // if the result is empty which means no intent, re-send it to the current skill + if (recognizedSkillManifestRecognized != null + && !string.Equals(recognizedSkillManifestRecognized, this.Id)) + { + if (_skillIntentRecognizer.ConfirmIntentSwitch) + { + var options = new SkillSwitchConfirmOption() + { + FallbackHandledEvent = lastEvent, + TargetIntent = recognizedSkillManifestRecognized, + UserInputActivity = innerDc.Context.Activity, + }; + + return await innerDc.BeginDialogAsync(DialogIds.ConfirmSkillSwitchFlow, options); + } + + await _skillTransport.CancelRemoteDialogsAsync(_skillManifest, _serviceClientCredentials, innerDc.Context); + return await innerDc.EndDialogAsync(recognizedSkillManifestRecognized); + } + } + } - return dialogResult; + dialogResult = await ForwardToSkillAsync(innerDc, lastEvent); + } + + return dialogResult; } } catch @@ -278,5 +405,29 @@ private Action GetTokenRequestCallback(DialogContext dialogContext) } }; } + + private Action GetFallbackCallback(DialogContext dialogContext) + { + return (activity) => + { + // Send trace to emulator + dialogContext.Context.SendActivityAsync(new Activity(type: ActivityTypes.Trace, text: $"<--Received a fallback request from a skill")).GetAwaiter().GetResult(); + + var fallbackEvent = activity.CreateReply(); + fallbackEvent.Type = ActivityTypes.Event; + fallbackEvent.Name = SkillEvents.FallbackEventName; + + lock (_lockObject) + { + _queuedResponses.Enqueue(fallbackEvent); + } + }; + } + + private class DialogIds + { + public const string ConfirmSkillSwitchPrompt = "confirmSkillSwitchPrompt"; + public const string ConfirmSkillSwitchFlow = "confirmSkillSwitchFlow"; + } } } \ No newline at end of file diff --git a/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills/SkillSwitchConfirmOption.cs b/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills/SkillSwitchConfirmOption.cs new file mode 100644 index 0000000000..b8f496ecb1 --- /dev/null +++ b/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills/SkillSwitchConfirmOption.cs @@ -0,0 +1,13 @@ +using Microsoft.Bot.Schema; + +namespace Microsoft.Bot.Builder.Skills +{ + public class SkillSwitchConfirmOption + { + public Activity FallbackHandledEvent { get; set; } + + public string TargetIntent { get; set; } + + public Activity UserInputActivity { get; set; } + } +} diff --git a/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills/SkillWebSocketBotAdapter.cs b/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills/SkillWebSocketBotAdapter.cs index 1d202939d6..a34d8569b5 100644 --- a/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills/SkillWebSocketBotAdapter.cs +++ b/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills/SkillWebSocketBotAdapter.cs @@ -3,6 +3,7 @@ using System.Net; using System.Threading; using System.Threading.Tasks; +using Microsoft.Bot.Builder.Skills.Models; using Microsoft.Bot.Builder.Solutions; using Microsoft.Bot.Schema; using Microsoft.Bot.StreamingExtensions; @@ -17,7 +18,7 @@ namespace Microsoft.Bot.Builder.Skills /// 1. Process the incoming activity by calling into pipeline. /// 2. Implement BotAdapter protocol. Each method will send the activity back to calling bot using websocket. /// - public class SkillWebSocketBotAdapter : BotAdapter, IActivityHandler, IRemoteUserTokenProvider + public class SkillWebSocketBotAdapter : BotAdapter, IActivityHandler, IRemoteUserTokenProvider, IFallbackRequestProvider { private readonly IBotTelemetryClient _botTelemetryClient; @@ -232,6 +233,17 @@ public async Task SendRemoteTokenRequestEventAsync(ITurnContext turnContext, Can await SendActivitiesAsync(turnContext, new Activity[] { response }, cancellationToken).ConfigureAwait(false); } + public async Task SendRemoteFallbackEventAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + // We trigger a Fallback Request from the Parent Bot by sending a "skill/fallbackRequest" event + var response = turnContext.Activity.CreateReply(); + response.Type = ActivityTypes.Event; + response.Name = SkillEvents.FallbackEventName; + + // Send the fallback Event + await SendActivitiesAsync(turnContext, new Activity[] { response }, cancellationToken).ConfigureAwait(false); + } + private async Task SendRequestAsync(StreamingRequest request, CancellationToken cancellation = default(CancellationToken)) { try diff --git a/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills/SkillWebSocketTransport.cs b/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills/SkillWebSocketTransport.cs index 6776241e7e..796019e186 100644 --- a/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills/SkillWebSocketTransport.cs +++ b/lib/csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills/SkillWebSocketTransport.cs @@ -29,7 +29,7 @@ public SkillWebSocketTransport( _streamingTransportClient = streamingTransportClient; } - public async Task ForwardToSkillAsync(SkillManifest skillManifest, IServiceClientCredentials serviceClientCredentials, ITurnContext turnContext, Activity activity, Action tokenRequestHandler = null) + public async Task ForwardToSkillAsync(SkillManifest skillManifest, IServiceClientCredentials serviceClientCredentials, ITurnContext turnContext, Activity activity, Action tokenRequestHandler = null, Action fallbackHandler = null) { if (_streamingTransportClient == null) { @@ -48,6 +48,7 @@ public async Task ForwardToSkillAsync(SkillManifest skillManifest, IServic turnContext, _botTelemetryClient, GetTokenCallback(turnContext, tokenRequestHandler), + GetFallbackCallback(turnContext, fallbackHandler), GetHandoffActivityCallback()), headers); } @@ -96,6 +97,14 @@ private Action GetTokenCallback(ITurnContext turnContext, Action GetFallbackCallback(ITurnContext turnContext, Action fallbackEventHandler) + { + return (activity) => + { + fallbackEventHandler?.Invoke(activity); + }; + } + private Action GetHandoffActivityCallback() { return (activity) => diff --git a/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Dialogs/RouterDialog.cs b/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Dialogs/RouterDialog.cs index a9bd6e8de7..862feb9560 100644 --- a/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Dialogs/RouterDialog.cs +++ b/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Dialogs/RouterDialog.cs @@ -66,6 +66,12 @@ public RouterDialog(string dialogId, IBotTelemetryClient telemetryClient) case DialogTurnStatus.Complete: { + if (result.Result is RouterDialogTurnResult routerDialogTurnResult && routerDialogTurnResult.Status == RouterDialogTurnStatus.Restart) + { + await RouteAsync(innerDc).ConfigureAwait(false); + break; + } + await CompleteAsync(innerDc).ConfigureAwait(false); // End active dialog diff --git a/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Dialogs/RouterDialogTurnResult.cs b/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Dialogs/RouterDialogTurnResult.cs new file mode 100644 index 0000000000..0d085ae664 --- /dev/null +++ b/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Dialogs/RouterDialogTurnResult.cs @@ -0,0 +1,15 @@ +namespace Microsoft.Bot.Builder.Solutions.Dialogs +{ + /// + /// Define router dialog turn result. + /// + public class RouterDialogTurnResult + { + public RouterDialogTurnResult(RouterDialogTurnStatus status) + { + this.Status = status; + } + + public RouterDialogTurnStatus Status { get; set; } + } +} diff --git a/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Dialogs/RouterDialogTurnStatus.cs b/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Dialogs/RouterDialogTurnStatus.cs new file mode 100644 index 0000000000..b3a08f20f2 --- /dev/null +++ b/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Dialogs/RouterDialogTurnStatus.cs @@ -0,0 +1,10 @@ +namespace Microsoft.Bot.Builder.Solutions.Dialogs +{ + /// + /// Define router dialog turn status. + /// + public enum RouterDialogTurnStatus + { + Restart = 0, + } +} diff --git a/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/IFallbackRequestProvider.cs b/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/IFallbackRequestProvider.cs new file mode 100644 index 0000000000..d5dd48505d --- /dev/null +++ b/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/IFallbackRequestProvider.cs @@ -0,0 +1,13 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Bot.Builder.Solutions +{ + /// + /// Interface that represents fallback request send behavior. + /// + public interface IFallbackRequestProvider + { + Task SendRemoteFallbackEventAsync(ITurnContext turnContext, CancellationToken cancellationToken); + } +} diff --git a/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Resources/CommonStrings.Designer.cs b/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Resources/CommonStrings.Designer.cs index 06f099bd0e..c9505cf163 100644 --- a/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Resources/CommonStrings.Designer.cs +++ b/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Resources/CommonStrings.Designer.cs @@ -96,6 +96,15 @@ public static string AtTimeDetailsFormat { } } + /// + /// Looks up a localized string similar to Are you sure to switch to {0}?. + /// + public static string ConfirmSkillSwitch { + get { + return ResourceManager.GetString("ConfirmSkillSwitch", resourceCulture); + } + } + /// /// Looks up a localized string similar to {0} all day. /// diff --git a/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Resources/CommonStrings.de.resx b/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Resources/CommonStrings.de.resx index 9eed3ec5cb..28992bb832 100644 --- a/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Resources/CommonStrings.de.resx +++ b/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Resources/CommonStrings.de.resx @@ -231,4 +231,7 @@ das jüngste ist {0}; + + Möchten Sie wirklich zu {0} wechseln? + \ No newline at end of file diff --git a/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Resources/CommonStrings.es.resx b/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Resources/CommonStrings.es.resx index 64316097a6..751f8144ce 100644 --- a/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Resources/CommonStrings.es.resx +++ b/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Resources/CommonStrings.es.resx @@ -231,4 +231,7 @@ el reciente es {0}; + + ¿Estás seguro de cambiar a {0}? + \ No newline at end of file diff --git a/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Resources/CommonStrings.fr.resx b/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Resources/CommonStrings.fr.resx index cfedd7d5fe..cba02c9e73 100644 --- a/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Resources/CommonStrings.fr.resx +++ b/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Resources/CommonStrings.fr.resx @@ -234,4 +234,7 @@ le dernier est {0}; + + Etes-vous sûr de passer à {0}? + \ No newline at end of file diff --git a/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Resources/CommonStrings.it.resx b/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Resources/CommonStrings.it.resx index fb46c69f3b..8b1d26dbd3 100644 --- a/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Resources/CommonStrings.it.resx +++ b/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Resources/CommonStrings.it.resx @@ -231,4 +231,7 @@ Il recente è {0}; + + Sei sicuro di passare a {0}? + \ No newline at end of file diff --git a/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Resources/CommonStrings.resx b/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Resources/CommonStrings.resx index 8e3f94eeca..ea90a99dcd 100644 --- a/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Resources/CommonStrings.resx +++ b/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Resources/CommonStrings.resx @@ -231,4 +231,7 @@ more + + Are you sure to switch to {0}? + \ No newline at end of file diff --git a/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Resources/CommonStrings.zh.resx b/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Resources/CommonStrings.zh.resx index c0bae3bc72..ea6c56a1e6 100644 --- a/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Resources/CommonStrings.zh.resx +++ b/lib/csharp/microsoft.bot.builder.solutions/microsoft.bot.builder.solutions/Resources/CommonStrings.zh.resx @@ -231,4 +231,7 @@ 最新的是{0}; + + 你确定要切换到{0}吗? + \ No newline at end of file