Updated FN Test projects to match samples (#4236)
* Fixes issue with InvokeResponse<T> hidding the underlying value for Body. Makes methods ins SkillHttpClient virtual so they can be mocked in tests. Updated BotFrameworkClient to use a deepclone of the activity rather than try/catch/finally to simply code and make sure we don't risk altering the original activity while the async request is being executed. Improved test coverage for BotFrameworkClient and SkillHttpClient. * Updated Skill test projects to match samples. * Deleted composer testing project
This commit is contained in:
Родитель
09db52ba4b
Коммит
22f10761e1
|
@ -1,4 +1,7 @@
|
|||
using Microsoft.AspNetCore.Hosting;
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.AdaptiveRootBot
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\libraries\integration\Microsoft.Bot.Builder.Integration.AspNet.Core\Microsoft.Bot.Builder.Integration.AspNet.Core.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\libraries\Microsoft.Bot.Builder.Dialogs.Adaptive\Microsoft.Bot.Builder.Dialogs.Adaptive.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\libraries\Microsoft.Bot.Builder.AI.LUIS\Microsoft.Bot.Builder.AI.Luis.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -8,7 +8,7 @@ using Microsoft.Bot.Builder.Dialogs;
|
|||
|
||||
namespace Microsoft.BotBuilderSamples.AdaptiveSkillBot.Bots
|
||||
{
|
||||
public class SkillBot<T> : IBot
|
||||
public class SkillBot<T> : ActivityHandler
|
||||
where T : Dialog
|
||||
{
|
||||
private readonly ConversationState _conversationState;
|
||||
|
@ -20,11 +20,11 @@ namespace Microsoft.BotBuilderSamples.AdaptiveSkillBot.Bots
|
|||
_dialogManager = new DialogManager(mainDialog);
|
||||
}
|
||||
|
||||
public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default)
|
||||
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await _dialogManager.OnTurnAsync(turnContext, cancellationToken);
|
||||
|
||||
// Save any state changes that might have occured during the turn.
|
||||
// Save any state changes that might have occurred during the turn.
|
||||
await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,11 +10,10 @@ using Microsoft.Bot.Builder;
|
|||
using Microsoft.Bot.Builder.Dialogs;
|
||||
using Microsoft.Bot.Builder.TraceExtensions;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Microsoft.BotBuilderSamples.AdaptiveSkillBot.Dialogs;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.AdaptiveSkillBot.Bots
|
||||
namespace Microsoft.BotBuilderSamples.AdaptiveSkillBot.Dialogs
|
||||
{
|
||||
/// <summary>
|
||||
/// A root dialog that can route activities sent to the skill to different dialogs.
|
|
@ -12,7 +12,7 @@ namespace Microsoft.BotBuilderSamples.AdaptiveSkillBot.Dialogs
|
|||
public class CancelAndHelpDialog : ComponentDialog
|
||||
{
|
||||
private const string HelpMsgText = "Show help here";
|
||||
private const string CancelMsgText = "Cancelling...";
|
||||
private const string CancelMsgText = "Canceling...";
|
||||
|
||||
public CancelAndHelpDialog(string id)
|
||||
: base(id)
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace Microsoft.BotBuilderSamples.AdaptiveSkillBot.Dialogs
|
|||
public class DateResolverDialog : CancelAndHelpDialog
|
||||
{
|
||||
private const string PromptMsgText = "When would you like to travel?";
|
||||
private const string RepromptMsgText = "I'm sorry, to make your booking please enter a full travel date including Day Month and Year.";
|
||||
private const string RepromptMsgText = "I'm sorry, to make your booking please enter a full travel date, including Day, Month, and Year.";
|
||||
|
||||
public DateResolverDialog(string id = null)
|
||||
: base(id ?? nameof(DateResolverDialog))
|
||||
|
@ -30,11 +30,11 @@ namespace Microsoft.BotBuilderSamples.AdaptiveSkillBot.Dialogs
|
|||
{
|
||||
if (promptContext.Recognized.Succeeded)
|
||||
{
|
||||
// This value will be a TIMEX. And we are only interested in a Date so grab the first result and drop the Time part.
|
||||
// TIMEX is a format that represents DateTime expressions that include some ambiguity. e.g. missing a Year.
|
||||
// This value will be a TIMEX. We are only interested in the Date part, so grab the first result and drop the Time part.
|
||||
// TIMEX is a format that represents DateTime expressions that include some ambiguity, such as a missing Year.
|
||||
var timex = promptContext.Recognized.Value[0].Timex.Split('T')[0];
|
||||
|
||||
// If this is a definite Date including year, month and day we are good otherwise reprompt.
|
||||
// If this is a definite Date that includes year, month and day we are good; otherwise, reprompt.
|
||||
// A better solution might be to let the user know what part is actually missing.
|
||||
var isDefinite = new TimexProperty(timex).Types.Contains(Constants.TimexTypes.Definite);
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ namespace Microsoft.BotBuilderSamples.AdaptiveSkillBot.Dialogs
|
|||
}
|
||||
}
|
||||
|
||||
// Returns true if luis is configured in the appsettings.json and initialized.
|
||||
// Returns true if LUIS is configured in the appsettings.json and initialized.
|
||||
public virtual bool IsConfigured => _recognizer != null;
|
||||
|
||||
public virtual async Task<RecognizerResult> RecognizeAsync(ITurnContext turnContext, CancellationToken cancellationToken)
|
||||
|
|
|
@ -13,5 +13,12 @@
|
|||
|
||||
"LuisAppId": "",
|
||||
"LuisAPIKey": "",
|
||||
"LuisAPIHostName": ""
|
||||
"LuisAPIHostName": "",
|
||||
|
||||
// This is a comma separate list with the App IDs that will have access to the skill.
|
||||
// This setting is used in AllowedCallersClaimsValidator.
|
||||
// Examples:
|
||||
// [ "*" ] allows all callers.
|
||||
// [ "AppId1", "AppId2" ] only allows access to parent bots with "AppId1" and "AppId2".
|
||||
"AllowedCallers": [ "*" ]
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"$id": "DialogSkillBot",
|
||||
"name": "Skill bot with dialogs",
|
||||
"version": "1.0",
|
||||
"description": "This is a sample skill definition for multiple activity types",
|
||||
"description": "This is a sample skill definition for multiple activity types.",
|
||||
"publisherName": "Microsoft",
|
||||
"privacyUrl": "https://dialogskillbot.contoso.com/privacy.html",
|
||||
"copyright": "Copyright (c) Microsoft Corporation. All rights reserved.",
|
||||
|
@ -19,14 +19,14 @@
|
|||
{
|
||||
"name": "default",
|
||||
"protocol": "BotFrameworkV3",
|
||||
"description": "Default endpoint for the skill",
|
||||
"endpointUrl": "https://ggdialogskillbot.azurewebsites.net/api/messages",
|
||||
"msAppId": "f3fe8762-e50c-4688-b202-a040f522d916"
|
||||
"description": "Default endpoint for the skill.",
|
||||
"endpointUrl": "https://dialogskillbot.contoso.com/api/messages",
|
||||
"msAppId": "00000000-0000-0000-0000-000000000000"
|
||||
}
|
||||
],
|
||||
"activities": {
|
||||
"bookFlight": {
|
||||
"description": "Books a flight (multi turn)",
|
||||
"description": "Books a flight (multi turn).",
|
||||
"type": "event",
|
||||
"name": "BookFlight",
|
||||
"value": {
|
||||
|
@ -36,9 +36,14 @@
|
|||
"$ref": "#/definitions/bookingInfo"
|
||||
}
|
||||
},
|
||||
"oauthTest": {
|
||||
"description": "Tests the OAuth prompt in a skill",
|
||||
"type": "event",
|
||||
"name": "OAuthTest"
|
||||
},
|
||||
"getWeather": {
|
||||
"description": "Retrieves and returns the weather for the user's location (single turn, invoke)",
|
||||
"type": "invoke",
|
||||
"description": "Retrieves and returns the weather for the user's location.",
|
||||
"type": "event",
|
||||
"name": "GetWeather",
|
||||
"value": {
|
||||
"$ref": "#/definitions/location"
|
||||
|
@ -49,22 +54,13 @@
|
|||
},
|
||||
"passthroughMessage": {
|
||||
"type": "message",
|
||||
"description": "Receives the user's utterance and attempts to resolve it using the skill's LUIS models",
|
||||
"description": "Receives the user's utterance and attempts to resolve it using the skill's LUIS models.",
|
||||
"value": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"localeValue": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"locale": {
|
||||
"type": "string",
|
||||
"description": "The current user's locale ISO code"
|
||||
}
|
||||
}
|
||||
},
|
||||
"bookingInfo": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
@ -73,15 +69,15 @@
|
|||
"properties": {
|
||||
"origin": {
|
||||
"type": "string",
|
||||
"description": "this is the origin city for the flight"
|
||||
"description": "This is the origin city for the flight."
|
||||
},
|
||||
"destination": {
|
||||
"type": "string",
|
||||
"description": "this is the destination city for the flight"
|
||||
"description": "This is the destination city for the flight."
|
||||
},
|
||||
"travelDate": {
|
||||
"type": "string",
|
||||
"description": "The date for the flight in YYYY-MM-DD format"
|
||||
"description": "The date for the flight in YYYY-MM-DD format."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -96,7 +92,7 @@
|
|||
},
|
||||
"location": {
|
||||
"type": "object",
|
||||
"description": "Location metadata",
|
||||
"description": "Location metadata.",
|
||||
"properties": {
|
||||
"latitude": {
|
||||
"type": "number",
|
||||
|
|
|
@ -26,21 +26,27 @@ namespace Microsoft.BotBuilderSamples.DialogEchoSkillBot.Authentication
|
|||
throw new ArgumentNullException(nameof(config));
|
||||
}
|
||||
|
||||
// AllowedCallers is the setting in appsettings.json file
|
||||
// that consists of the list of parent bot ids that are allowed to access the skill
|
||||
// to add a new parent bot simply go to the AllowedCallers and add
|
||||
// the parent bot's microsoft app id to the list
|
||||
// AllowedCallers is the setting in the appsettings.json file
|
||||
// that consists of the list of parent bot IDs that are allowed to access the skill.
|
||||
// To add a new parent bot, simply edit the AllowedCallers and add
|
||||
// the parent bot's Microsoft app ID to the list.
|
||||
// In this sample, we allow all callers if AllowedCallers contains an "*".
|
||||
var section = config.GetSection(ConfigKey);
|
||||
var appsList = section.Get<string[]>();
|
||||
_allowedCallers = appsList != null ? new List<string>(appsList) : null;
|
||||
if (appsList == null)
|
||||
{
|
||||
throw new ArgumentNullException($"\"{ConfigKey}\" not found in configuration.");
|
||||
}
|
||||
|
||||
_allowedCallers = new List<string>(appsList);
|
||||
}
|
||||
|
||||
public override Task ValidateClaimsAsync(IList<Claim> claims)
|
||||
{
|
||||
// if _allowedCallers is null we allow all calls
|
||||
if (_allowedCallers != null && SkillValidation.IsSkillClaim(claims))
|
||||
// If _allowedCallers contains an "*", we allow all callers.
|
||||
if (SkillValidation.IsSkillClaim(claims) && !_allowedCallers.Contains("*"))
|
||||
{
|
||||
// Check that the appId claim in the skill request is in the list of skills configured for this bot.
|
||||
// Check that the appId claim in the skill request is in the list of callers configured for this bot.
|
||||
var appId = JwtTokenValidation.GetAppIdFromClaims(claims);
|
||||
if (!_allowedCallers.Contains(appId))
|
||||
{
|
||||
|
|
|
@ -23,11 +23,19 @@ namespace Microsoft.BotBuilderSamples.DialogEchoSkillBot.Bots
|
|||
}
|
||||
else
|
||||
{
|
||||
var messageText = $"Echo (dotnet core 3.1) : {turnContext.Activity.Text}";
|
||||
var messageText = $"Echo: {turnContext.Activity.Text}";
|
||||
await turnContext.SendActivityAsync(MessageFactory.Text(messageText, messageText, InputHints.IgnoringInput), cancellationToken);
|
||||
messageText = "Say \"end\" or \"stop\" and I'll end the conversation and back to the parent.";
|
||||
await turnContext.SendActivityAsync(MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput), cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
protected override Task OnEndOfConversationActivityAsync(ITurnContext<IEndOfConversationActivity> turnContext, CancellationToken cancellationToken)
|
||||
{
|
||||
// This will be called if the root bot is ending the conversation. Sending additional messages should be
|
||||
// avoided as the conversation may have been deleted.
|
||||
// Perform cleanup of resources if needed.
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
<LangVersion>latest</LangVersion>
|
||||
<AssemblyName>Microsoft.BotBuilderSamples.DialogEchoSkillBot</AssemblyName>
|
||||
<RootNamespace>Microsoft.BotBuilderSamples.DialogEchoSkillBot</RootNamespace>
|
||||
<UserSecretsId>a6aa19d1-4134-48c1-8970-8404e694e003</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.DialogEchoSkillBot
|
||||
{
|
||||
|
@ -17,6 +18,11 @@ namespace Microsoft.BotBuilderSamples.DialogEchoSkillBot
|
|||
Host.CreateDefaultBuilder(args)
|
||||
.ConfigureWebHostDefaults(webBuilder =>
|
||||
{
|
||||
webBuilder.ConfigureLogging((logging) =>
|
||||
{
|
||||
logging.AddDebug();
|
||||
logging.AddConsole();
|
||||
});
|
||||
webBuilder.UseStartup<Startup>();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.Bot.Builder.TraceExtensions;
|
||||
|
@ -14,15 +15,29 @@ namespace Microsoft.BotBuilderSamples.DialogEchoSkillBot
|
|||
{
|
||||
public class SkillAdapterWithErrorHandler : BotFrameworkHttpAdapter
|
||||
{
|
||||
public SkillAdapterWithErrorHandler(IConfiguration configuration, ICredentialProvider credentialProvider, AuthenticationConfiguration authConfig, ILogger<BotFrameworkHttpAdapter> logger, ConversationState conversationState = null)
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public SkillAdapterWithErrorHandler(IConfiguration configuration, ICredentialProvider credentialProvider, AuthenticationConfiguration authConfig, ILogger<BotFrameworkHttpAdapter> logger)
|
||||
: base(configuration, credentialProvider, authConfig, logger: logger)
|
||||
{
|
||||
OnTurnError = async (turnContext, exception) =>
|
||||
{
|
||||
// Log any leaked exception from the application.
|
||||
logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}");
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
OnTurnError = HandleTurnError;
|
||||
}
|
||||
|
||||
// Send a message to the user
|
||||
private async Task HandleTurnError(ITurnContext turnContext, Exception exception)
|
||||
{
|
||||
// Log any leaked exception from the application.
|
||||
_logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}");
|
||||
|
||||
await SendErrorMessageAsync(turnContext, exception);
|
||||
await SendEoCToParentAsync(turnContext, exception);
|
||||
}
|
||||
|
||||
private async Task SendErrorMessageAsync(ITurnContext turnContext, Exception exception)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Send a message to the user.
|
||||
var errorMessageText = "The skill encountered an error or bug.";
|
||||
var errorMessage = MessageFactory.Text(errorMessageText, errorMessageText, InputHints.IgnoringInput);
|
||||
await turnContext.SendActivityAsync(errorMessage);
|
||||
|
@ -31,32 +46,32 @@ namespace Microsoft.BotBuilderSamples.DialogEchoSkillBot
|
|||
errorMessage = MessageFactory.Text(errorMessageText, errorMessageText, InputHints.ExpectingInput);
|
||||
await turnContext.SendActivityAsync(errorMessage);
|
||||
|
||||
if (conversationState != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Delete the conversationState for the current conversation to prevent the
|
||||
// bot from getting stuck in a error-loop caused by being in a bad state.
|
||||
// ConversationState should be thought of as similar to "cookie-state" in a Web pages.
|
||||
await conversationState.DeleteAsync(turnContext);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, $"Exception caught on attempting to Delete ConversationState : {ex}");
|
||||
}
|
||||
}
|
||||
// Send a trace activity, which will be displayed in the Bot Framework Emulator.
|
||||
// Note: we return the entire exception in the value property to help the developer;
|
||||
// this should not be done in production.
|
||||
await turnContext.TraceActivityAsync("OnTurnError Trace", exception.ToString(), "https://www.botframework.com/schemas/error", "TurnError");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Exception caught in SendErrorMessageAsync : {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
// Send and EndOfConversation activity to the skill caller with the error to end the conversation
|
||||
private async Task SendEoCToParentAsync(ITurnContext turnContext, Exception exception)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Send an EndOfConversation activity to the skill caller with the error to end the conversation,
|
||||
// and let the caller decide what to do.
|
||||
var endOfConversation = Activity.CreateEndOfConversationActivity();
|
||||
endOfConversation.Code = "SkillError";
|
||||
endOfConversation.Text = exception.Message;
|
||||
await turnContext.SendActivityAsync(endOfConversation);
|
||||
|
||||
// Send a trace activity, which will be displayed in the Bot Framework Emulator
|
||||
// Note: we return the entire exception in the value property to help the developer, this should not be done in prod.
|
||||
await turnContext.TraceActivityAsync("OnTurnError Trace", exception.ToString(), "https://www.botframework.com/schemas/error", "TurnError");
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Exception caught in SendEoCToParentAsync : {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,18 +17,10 @@ namespace Microsoft.BotBuilderSamples.DialogEchoSkillBot
|
|||
{
|
||||
public class Startup
|
||||
{
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
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.AddControllers()
|
||||
.AddNewtonsoftJson();
|
||||
services.AddControllers().AddNewtonsoftJson();
|
||||
|
||||
// Configure credentials
|
||||
services.AddSingleton<ICredentialProvider, ConfigurationCredentialProvider>();
|
||||
|
@ -51,21 +43,14 @@ namespace Microsoft.BotBuilderSamples.DialogEchoSkillBot
|
|||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.UseDefaultFiles();
|
||||
app.UseStaticFiles();
|
||||
|
||||
//app.UseHttpsRedirection(); Enable this to support https
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllers();
|
||||
});
|
||||
app.UseDefaultFiles()
|
||||
.UseStaticFiles()
|
||||
.UseRouting()
|
||||
.UseAuthorization()
|
||||
.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllers();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,5 +9,11 @@
|
|||
"AllowedHosts": "*",
|
||||
"MicrosoftAppId": "",
|
||||
"MicrosoftAppPassword": "",
|
||||
"AllowedCallers": []
|
||||
|
||||
// This is a comma separate list with the App IDs that will have access to the skill.
|
||||
// This setting is used in AllowedCallersClaimsValidator.
|
||||
// Examples:
|
||||
// [ "*" ] allows all callers.
|
||||
// [ "AppId1", "AppId2" ] only allows access to parent bots with "AppId1" and "AppId2".
|
||||
"AllowedCallers": [ "*" ]
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Bot.Builder;
|
||||
|
@ -28,9 +29,17 @@ namespace Microsoft.BotBuilderSamples.DialogRootBot.Controllers
|
|||
[HttpGet]
|
||||
public async Task PostAsync()
|
||||
{
|
||||
// Delegate the processing of the HTTP POST to the adapter.
|
||||
// The adapter will invoke the bot.
|
||||
await _adapter.ProcessAsync(Request, Response, _bot);
|
||||
try
|
||||
{
|
||||
// Delegate the processing of the HTTP POST to the adapter.
|
||||
// The adapter will invoke the bot.
|
||||
await _adapter.ProcessAsync(Request, Response, _bot);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.DialogRootBot.Dialogs
|
||||
{
|
||||
public class BookingDetails
|
||||
{
|
||||
[JsonProperty("destination")]
|
||||
public string Destination { get; set; }
|
||||
|
||||
[JsonProperty("origin")]
|
||||
public string Origin { get; set; }
|
||||
|
||||
[JsonProperty("travelDate")]
|
||||
public string TravelDate { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.DialogRootBot.Dialogs
|
||||
{
|
||||
public class Location
|
||||
{
|
||||
[JsonProperty("latitude")]
|
||||
public float? Latitude { get; set; }
|
||||
|
||||
[JsonProperty("longitude")]
|
||||
public float? Longitude { get; set; }
|
||||
|
||||
[JsonProperty("postalCode")]
|
||||
public string PostalCode { get; set; }
|
||||
}
|
||||
}
|
|
@ -2,7 +2,6 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -15,7 +14,6 @@ using Microsoft.Bot.Connector.Authentication;
|
|||
using Microsoft.Bot.Schema;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.DialogRootBot.Dialogs
|
||||
{
|
||||
|
@ -24,15 +22,11 @@ namespace Microsoft.BotBuilderSamples.DialogRootBot.Dialogs
|
|||
/// </summary>
|
||||
public class MainDialog : ComponentDialog
|
||||
{
|
||||
// State property key that stores the active skill (used in AdapterWithErrorHandler to terminate the skills on error).
|
||||
public static readonly string ActiveSkillPropertyName = $"{typeof(MainDialog).FullName}.ActiveSkillProperty";
|
||||
|
||||
// Constants used for selecting actions on the skill.
|
||||
private const string SkillActionBookFlight = "BookFlight";
|
||||
private const string SkillActionBookFlightWithInputParameters = "BookFlight with input parameters";
|
||||
private const string SkillActionGetWeather = "GetWeather";
|
||||
private const string SkillActionOAuthTest = "OAuthTest";
|
||||
private const string SkillActionEchoSkillBot = "EchoSkill";
|
||||
private const string SkillActionMessage = "Message";
|
||||
private const string JustForwardTheActivity = "JustForwardTurnContext.Activity";
|
||||
|
||||
private readonly IStatePropertyAccessor<BotFrameworkSkill> _activeSkillProperty;
|
||||
private readonly string _selectedSkillKey = $"{typeof(MainDialog).FullName}.SelectedSkillKey";
|
||||
|
@ -60,10 +54,10 @@ namespace Microsoft.BotBuilderSamples.DialogRootBot.Dialogs
|
|||
throw new ArgumentNullException(nameof(conversationState));
|
||||
}
|
||||
|
||||
// Register the tangent
|
||||
// Register the tangent dialog for testing tangents and resume
|
||||
AddDialog(new TangentDialog());
|
||||
|
||||
// Use helper method to add SkillDialog instances for the configured skills.
|
||||
// Create and add SkillDialog instances for the configured skills.
|
||||
AddSkillDialogs(conversationState, conversationIdFactory, skillClient, skillsConfig, botId);
|
||||
|
||||
// Add ChoicePrompt to render available skills.
|
||||
|
@ -89,6 +83,14 @@ namespace Microsoft.BotBuilderSamples.DialogRootBot.Dialogs
|
|||
InitialDialogId = nameof(WaterfallDialog);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This override is used to test the "abort" command to interrupt skills from the parent and
|
||||
/// also to test the "tangent" command to start a tangent and resume a skill.
|
||||
/// </summary>
|
||||
/// <param name="innerDc">The inner <see cref="DialogContext"/> for the current turn of conversation.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used by other objects
|
||||
/// or threads to receive notice of cancellation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
protected override async Task<DialogTurnResult> OnContinueDialogAsync(DialogContext innerDc, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// This is an example on how to cancel a SkillDialog that is currently in progress from the parent bot.
|
||||
|
@ -130,22 +132,22 @@ namespace Microsoft.BotBuilderSamples.DialogRootBot.Dialogs
|
|||
return await stepContext.PromptAsync("SkillPrompt", options, cancellationToken);
|
||||
}
|
||||
|
||||
// Render a prompt to select the action for the skill.
|
||||
// Render a prompt to select the begin action for the skill.
|
||||
private async Task<DialogTurnResult> SelectSkillActionStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
|
||||
{
|
||||
// Get the skill info based on the selected skill.
|
||||
var selectedSkillId = ((FoundChoice)stepContext.Result).Value;
|
||||
var selectedSkill = _skillsConfig.Skills.FirstOrDefault(s => s.Value.Id == selectedSkillId).Value;
|
||||
var selectedSkill = _skillsConfig.Skills.FirstOrDefault(keyValuePair => keyValuePair.Value.Id == selectedSkillId).Value;
|
||||
|
||||
// Remember the skill selected by the user.
|
||||
stepContext.Values[_selectedSkillKey] = selectedSkill;
|
||||
|
||||
// Create the PromptOptions with the actions supported by the selected skill.
|
||||
var messageText = $"Select an action # to send to **{selectedSkill.Id}** or just type in a message and it will be forwarded to the skill";
|
||||
var messageText = $"Select an action # to send to **{selectedSkill.Id}**.\n\nOr just type in a message and it will be forwarded to the skill as a message activity.";
|
||||
var options = new PromptOptions
|
||||
{
|
||||
Prompt = MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput),
|
||||
Choices = GetSkillActions(selectedSkill)
|
||||
Choices = selectedSkill.GetActions().Select(action => new Choice(action)).ToList()
|
||||
};
|
||||
|
||||
// Prompt the user to select a skill action.
|
||||
|
@ -158,7 +160,7 @@ namespace Microsoft.BotBuilderSamples.DialogRootBot.Dialogs
|
|||
if (!promptContext.Recognized.Succeeded)
|
||||
{
|
||||
// Assume the user wants to send a message if an item in the list is not selected.
|
||||
promptContext.Recognized.Value = new FoundChoice { Value = SkillActionMessage };
|
||||
promptContext.Recognized.Value = new FoundChoice { Value = JustForwardTheActivity };
|
||||
}
|
||||
|
||||
return Task.FromResult(true);
|
||||
|
@ -169,26 +171,13 @@ namespace Microsoft.BotBuilderSamples.DialogRootBot.Dialogs
|
|||
{
|
||||
var selectedSkill = (BotFrameworkSkill)stepContext.Values[_selectedSkillKey];
|
||||
|
||||
Activity skillActivity;
|
||||
switch (selectedSkill.Id)
|
||||
{
|
||||
case "EchoSkillBot":
|
||||
// Echo only takes messages
|
||||
skillActivity = CreateDialogSkillBotActivity(SkillActionMessage, stepContext.Context);
|
||||
|
||||
break;
|
||||
case "DialogSkillBot":
|
||||
skillActivity = CreateDialogSkillBotActivity(((FoundChoice)stepContext.Result).Value, stepContext.Context);
|
||||
break;
|
||||
default:
|
||||
throw new Exception($"Unknown target skill id: {selectedSkill.Id}.");
|
||||
}
|
||||
var skillActivity = CreateBeginActivity(stepContext.Context, selectedSkill.Id, ((FoundChoice)stepContext.Result).Value);
|
||||
|
||||
// Create the BeginSkillDialogOptions and assign the activity to send.
|
||||
var skillDialogArgs = new BeginSkillDialogOptions { Activity = skillActivity };
|
||||
|
||||
// Comment or uncomment this line if you need to enable or disabled buffered replies.
|
||||
//skillDialogArgs.Activity.DeliveryMode = DeliveryModes.ExpectReplies;
|
||||
// skillDialogArgs.Activity.DeliveryMode = DeliveryModes.ExpectReplies;
|
||||
|
||||
// Save active skill in state.
|
||||
await _activeSkillProperty.SetAsync(stepContext.Context, selectedSkill, cancellationToken);
|
||||
|
@ -241,90 +230,18 @@ namespace Microsoft.BotBuilderSamples.DialogRootBot.Dialogs
|
|||
}
|
||||
}
|
||||
|
||||
// Helper method to create Choice elements for the actions supported by the skill.
|
||||
private IList<Choice> GetSkillActions(BotFrameworkSkill skill)
|
||||
{
|
||||
// Note: the bot would probably render this by reading the skill manifest.
|
||||
// We are just using hardcoded skill actions here for simplicity.
|
||||
|
||||
var choices = new List<Choice>();
|
||||
switch (skill.Id)
|
||||
{
|
||||
case "EchoSkillBot":
|
||||
choices.Add(new Choice(SkillActionMessage));
|
||||
break;
|
||||
|
||||
case "DialogSkillBot":
|
||||
choices.Add(new Choice(SkillActionBookFlight));
|
||||
choices.Add(new Choice(SkillActionBookFlightWithInputParameters));
|
||||
choices.Add(new Choice(SkillActionGetWeather));
|
||||
choices.Add(new Choice(SkillActionOAuthTest));
|
||||
choices.Add(new Choice(SkillActionEchoSkillBot));
|
||||
break;
|
||||
}
|
||||
|
||||
return choices;
|
||||
}
|
||||
|
||||
// Helper method to create the activity to be sent to the DialogSkillBot using selected type and values.
|
||||
private Activity CreateDialogSkillBotActivity(string selectedOption, ITurnContext turnContext)
|
||||
private Activity CreateBeginActivity(ITurnContext turnContext, string skillId, string selectedOption)
|
||||
{
|
||||
// Note: in a real bot, the dialogArgs will be created dynamically based on the conversation
|
||||
// and what each action requires; here we hardcode the values to make things simpler.
|
||||
|
||||
// Just forward the message activity to the skill with whatever the user said.
|
||||
if (selectedOption.Equals(SkillActionMessage, StringComparison.CurrentCultureIgnoreCase))
|
||||
if (selectedOption.Equals(JustForwardTheActivity, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
// Note message activities also support input parameters but we are not using them in this example.
|
||||
return turnContext.Activity;
|
||||
// Return a deep clone of the activity so we don't risk altering the original one
|
||||
return ObjectPath.Clone(turnContext.Activity);
|
||||
}
|
||||
|
||||
Activity activity = null;
|
||||
|
||||
// Send an event activity to the skill with "BookFlight" in the name.
|
||||
if (selectedOption.Equals(SkillActionBookFlight, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
activity = (Activity)Activity.CreateEventActivity();
|
||||
activity.Name = SkillActionBookFlight;
|
||||
}
|
||||
|
||||
// Send an event activity to the skill with "BookFlight" in the name and some testing values.
|
||||
if (selectedOption.Equals(SkillActionBookFlightWithInputParameters, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
activity = (Activity)Activity.CreateEventActivity();
|
||||
activity.Name = SkillActionBookFlight;
|
||||
activity.Value = JObject.Parse("{ \"origin\": \"New York\", \"destination\": \"Seattle\"}");
|
||||
}
|
||||
|
||||
// Send an event activity to the skill with "GetWeather" in the name and some testing values.
|
||||
if (selectedOption.Equals(SkillActionGetWeather, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
activity = (Activity)Activity.CreateEventActivity();
|
||||
activity.Name = SkillActionGetWeather;
|
||||
activity.Value = JObject.Parse("{ \"latitude\": 47.614891, \"longitude\": -122.195801}");
|
||||
return activity;
|
||||
}
|
||||
|
||||
// Send an event activity to the skill with "OAuthTest" in the name.
|
||||
if (selectedOption.Equals(SkillActionOAuthTest, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
activity = (Activity)Activity.CreateEventActivity();
|
||||
activity.Name = SkillActionOAuthTest;
|
||||
return activity;
|
||||
}
|
||||
|
||||
// Send an event activity to the skill with "EchoSkillBot" in the name.
|
||||
if (selectedOption.Equals(SkillActionEchoSkillBot, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
activity = (Activity)Activity.CreateEventActivity();
|
||||
activity.Name = SkillActionEchoSkillBot;
|
||||
return activity;
|
||||
}
|
||||
|
||||
if (activity == null)
|
||||
{
|
||||
throw new Exception($"Unable to create dialogArgs for \"{selectedOption}\".");
|
||||
}
|
||||
// Get the begin activity from the skill instance.
|
||||
var activity = _skillsConfig.Skills[skillId].CreateBeginActivity(selectedOption);
|
||||
|
||||
// We are manually creating the activity to send to the skill; ensure we add the ChannelData and Properties
|
||||
// from the original activity so the skill gets them.
|
||||
|
|
|
@ -9,6 +9,9 @@ using Microsoft.Bot.Schema;
|
|||
|
||||
namespace Microsoft.BotBuilderSamples.DialogRootBot.Dialogs
|
||||
{
|
||||
/// <summary>
|
||||
/// A simple waterfall dialog used to test triggering tangents from <see cref="MainDialog"/>.
|
||||
/// </summary>
|
||||
public class TangentDialog : ComponentDialog
|
||||
{
|
||||
public TangentDialog(string dialogId = nameof(TangentDialog))
|
||||
|
@ -18,7 +21,8 @@ namespace Microsoft.BotBuilderSamples.DialogRootBot.Dialogs
|
|||
var waterfallSteps = new WaterfallStep[]
|
||||
{
|
||||
Step1Async,
|
||||
Step2Async
|
||||
Step2Async,
|
||||
EndStepAsync
|
||||
};
|
||||
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), waterfallSteps));
|
||||
|
||||
|
@ -27,14 +31,21 @@ namespace Microsoft.BotBuilderSamples.DialogRootBot.Dialogs
|
|||
|
||||
private async Task<DialogTurnResult> Step1Async(WaterfallStepContext stepContext, CancellationToken cancellationToken)
|
||||
{
|
||||
var promptMessage = MessageFactory.Text("Tangent step 1 of 2", InputHints.ExpectingInput);
|
||||
var messageText = "Tangent step 1 of 2, say something.";
|
||||
var promptMessage = MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput);
|
||||
return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = promptMessage }, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task<DialogTurnResult> Step2Async(WaterfallStepContext stepContext, CancellationToken cancellationToken)
|
||||
{
|
||||
var promptMessage = MessageFactory.Text("Tangent step 2 of 2", InputHints.ExpectingInput);
|
||||
var messageText = "Tangent step 2 of 2, say something.";
|
||||
var promptMessage = MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput);
|
||||
return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = promptMessage }, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task<DialogTurnResult> EndStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
|
||||
{
|
||||
return await stepContext.EndDialogAsync(cancellationToken: cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,10 +20,10 @@
|
|||
"DialogRootBot": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:5001;http://localhost:5000",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"applicationUrl": "http://localhost:5000"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.DialogRootBot.Skills
|
||||
{
|
||||
public class DialogSkill : SkillDefinition
|
||||
{
|
||||
private const string SkillActionBookFlight = "BookFlight";
|
||||
private const string SkillActionBookFlightWithInputParameters = "BookFlight with input parameters";
|
||||
private const string SkillActionGetWeather = "GetWeather";
|
||||
private const string SkillActionOAuthTest = "OAuthTest";
|
||||
private const string SkillActionEchoSkillBot = "EchoSkill (Root->DialogSkill->EchoSkill)";
|
||||
private const string SkillActionMessage = "Message (sends 'Book a flight' as message)";
|
||||
|
||||
public override IList<string> GetActions()
|
||||
{
|
||||
return new List<string>
|
||||
{
|
||||
SkillActionBookFlight,
|
||||
SkillActionBookFlightWithInputParameters,
|
||||
SkillActionGetWeather,
|
||||
SkillActionOAuthTest,
|
||||
SkillActionEchoSkillBot,
|
||||
SkillActionMessage
|
||||
};
|
||||
}
|
||||
|
||||
public override Activity CreateBeginActivity(string actionId)
|
||||
{
|
||||
Activity activity;
|
||||
|
||||
// Send an event activity to the skill with "BookFlight" in the name.
|
||||
if (actionId.Equals(SkillActionBookFlight, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
activity = (Activity)Activity.CreateEventActivity();
|
||||
activity.Name = SkillActionBookFlight;
|
||||
return activity;
|
||||
}
|
||||
|
||||
// Send an event activity to the skill with "BookFlight" in the name and some testing values.
|
||||
if (actionId.Equals(SkillActionBookFlightWithInputParameters, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
activity = (Activity)Activity.CreateEventActivity();
|
||||
activity.Name = SkillActionBookFlight;
|
||||
activity.Value = JObject.Parse("{ \"origin\": \"New York\", \"destination\": \"Seattle\"}");
|
||||
return activity;
|
||||
}
|
||||
|
||||
// Send an event activity to the skill with "GetWeather" in the name and some testing values.
|
||||
if (actionId.Equals(SkillActionGetWeather, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
activity = (Activity)Activity.CreateEventActivity();
|
||||
activity.Name = SkillActionGetWeather;
|
||||
activity.Value = JObject.Parse("{ \"latitude\": 47.614891, \"longitude\": -122.195801}");
|
||||
return activity;
|
||||
}
|
||||
|
||||
// Send an event activity to the skill with "OAuthTest" in the name.
|
||||
if (actionId.Equals(SkillActionOAuthTest, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
activity = (Activity)Activity.CreateEventActivity();
|
||||
activity.Name = SkillActionOAuthTest;
|
||||
return activity;
|
||||
}
|
||||
|
||||
// Send an event activity to the skill with "EchoSkillBot" in the name.
|
||||
if (actionId.Equals(SkillActionEchoSkillBot, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
activity = (Activity)Activity.CreateEventActivity();
|
||||
activity.Name = "EchoSkill";
|
||||
return activity;
|
||||
}
|
||||
|
||||
if (actionId.Equals(SkillActionMessage, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
activity = (Activity)Activity.CreateMessageActivity();
|
||||
activity.Name = SkillActionMessage;
|
||||
activity.Text = "Book a flight";
|
||||
return activity;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Unable to create begin activity for \"{actionId}\".");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.DialogRootBot.Skills
|
||||
{
|
||||
public class EchoSkill : SkillDefinition
|
||||
{
|
||||
private const string SkillActionMessage = "Message";
|
||||
|
||||
public override IList<string> GetActions()
|
||||
{
|
||||
return new List<string> { SkillActionMessage };
|
||||
}
|
||||
|
||||
public override Activity CreateBeginActivity(string actionId)
|
||||
{
|
||||
if (actionId.Equals(SkillActionMessage, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
var activity = (Activity)Activity.CreateMessageActivity();
|
||||
activity.Name = SkillActionMessage;
|
||||
activity.Text = "Begin the Echo Skill.";
|
||||
return activity;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Unable to create begin activity for \"{actionId}\".");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Bot.Builder.Skills;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.DialogRootBot.Skills
|
||||
{
|
||||
/// <summary>
|
||||
/// Extends <see cref="BotFrameworkSkill"/> and provides methods to return the actions and the begin activity to start a skill.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is just a temporary implementation, ideally, this should be replaced by logic that parses a manifest and creates
|
||||
/// what's needed.
|
||||
/// </remarks>
|
||||
public class SkillDefinition : BotFrameworkSkill
|
||||
{
|
||||
public virtual IList<string> GetActions()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public virtual Activity CreateBeginActivity(string actionId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.DialogRootBot.Skills
|
||||
{
|
||||
public class TeamsSkill : SkillDefinition
|
||||
{
|
||||
private const string SkillActionTeamsTaskModule = "TeamsTaskModule";
|
||||
private const string SkillActionTeamsCardAction = "TeamsCardAction";
|
||||
|
||||
public override IList<string> GetActions()
|
||||
{
|
||||
return new List<string>
|
||||
{
|
||||
SkillActionTeamsTaskModule,
|
||||
SkillActionTeamsCardAction
|
||||
};
|
||||
}
|
||||
|
||||
public override Activity CreateBeginActivity(string actionId)
|
||||
{
|
||||
Activity activity;
|
||||
|
||||
if (actionId.Equals(SkillActionTeamsTaskModule, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
activity = (Activity)Activity.CreateInvokeActivity();
|
||||
activity.Name = SkillActionTeamsTaskModule;
|
||||
return activity;
|
||||
}
|
||||
|
||||
if (actionId.Equals(SkillActionTeamsCardAction, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
activity = (Activity)Activity.CreateInvokeActivity();
|
||||
activity.Name = SkillActionTeamsCardAction;
|
||||
return activity;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Unable to create begin activity for \"{actionId}\".");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,7 +3,9 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Bot.Builder.Dialogs;
|
||||
using Microsoft.Bot.Builder.Skills;
|
||||
using Microsoft.BotBuilderSamples.DialogRootBot.Skills;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.DialogRootBot
|
||||
|
@ -21,7 +23,7 @@ namespace Microsoft.BotBuilderSamples.DialogRootBot
|
|||
{
|
||||
foreach (var skill in skills)
|
||||
{
|
||||
Skills.Add(skill.Id, skill);
|
||||
Skills.Add(skill.Id, CreateSkillDefinition(skill));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,6 +36,30 @@ namespace Microsoft.BotBuilderSamples.DialogRootBot
|
|||
|
||||
public Uri SkillHostEndpoint { get; }
|
||||
|
||||
public Dictionary<string, BotFrameworkSkill> Skills { get; } = new Dictionary<string, BotFrameworkSkill>();
|
||||
public Dictionary<string, SkillDefinition> Skills { get; } = new Dictionary<string, SkillDefinition>();
|
||||
|
||||
private static SkillDefinition CreateSkillDefinition(BotFrameworkSkill skill)
|
||||
{
|
||||
// Note: we hard code this for now, we should dynamically create instances based on the manifests.
|
||||
// For now, this code creates a strong typed version of the SkillDefinition and copies the info from
|
||||
// settings into it.
|
||||
SkillDefinition skillDefinition;
|
||||
switch (skill.Id)
|
||||
{
|
||||
case "EchoSkillBot":
|
||||
skillDefinition = ObjectPath.Assign<EchoSkill>(new EchoSkill(), skill);
|
||||
break;
|
||||
case "DialogSkillBot":
|
||||
skillDefinition = ObjectPath.Assign<DialogSkill>(new DialogSkill(), skill);
|
||||
break;
|
||||
case "TeamsSkillBot":
|
||||
skillDefinition = ObjectPath.Assign<TeamsSkill>(new TeamsSkill(), skill);
|
||||
break;
|
||||
default:
|
||||
throw new Exception($"Unable to find definition class for {skill.Id}.");
|
||||
}
|
||||
|
||||
return skillDefinition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,11 @@
|
|||
"Id": "DialogSkillBot",
|
||||
"AppId": "TODO: Add here the App ID for the skill",
|
||||
"SkillEndpoint": "http://localhost:39783/api/messages"
|
||||
},
|
||||
{
|
||||
"Id": "TeamsSkillBot",
|
||||
"AppId": "TODO: Add here the App ID for the skill",
|
||||
"SkillEndpoint": "http://localhost:39773/api/messages"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -5,7 +5,9 @@ using System.Linq;
|
|||
|
||||
namespace Microsoft.BotBuilderSamples.DialogSkillBot.CognitiveModels
|
||||
{
|
||||
// Extends the partial FlightBooking class with methods and properties that simplify accessing entities in the LUIS results.
|
||||
/// <summary>
|
||||
/// Extends the partial FlightBooking class with methods and properties that simplify accessing entities in the LUIS results.
|
||||
/// </summary>
|
||||
public partial class FlightBooking
|
||||
{
|
||||
public (string From, string Airport) FromEntities
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -16,6 +17,7 @@ using Microsoft.Bot.Schema;
|
|||
using Microsoft.BotBuilderSamples.DialogSkillBot.CognitiveModels;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.DialogSkillBot.Dialogs
|
||||
{
|
||||
|
@ -86,6 +88,9 @@ namespace Microsoft.BotBuilderSamples.DialogSkillBot.Dialogs
|
|||
case ActivityTypes.Message:
|
||||
return await OnMessageActivityAsync(stepContext, cancellationToken);
|
||||
|
||||
case ActivityTypes.Invoke:
|
||||
return await OnInvokeActivityAsync(stepContext, cancellationToken);
|
||||
|
||||
default:
|
||||
// We didn't get an activity type we can handle.
|
||||
await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Unrecognized ActivityType: \"{stepContext.Context.Activity.Type}\".", inputHint: InputHints.IgnoringInput), cancellationToken);
|
||||
|
@ -169,6 +174,32 @@ namespace Microsoft.BotBuilderSamples.DialogSkillBot.Dialogs
|
|||
|
||||
return new DialogTurnResult(DialogTurnStatus.Complete);
|
||||
}
|
||||
|
||||
private async Task<DialogTurnResult> OnInvokeActivityAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
|
||||
{
|
||||
var activity = stepContext.Context.Activity;
|
||||
await stepContext.Context.TraceActivityAsync($"{GetType().Name}.OnMessageActivityAsync()", label: $"Text: \"{activity.Text}\". Value: {GetObjectAsJsonString(activity.Value)}", cancellationToken: cancellationToken);
|
||||
|
||||
// Resolve what to execute based on the invoke activity name.
|
||||
switch (activity.Name)
|
||||
{
|
||||
case "InvokeForAStartTest":
|
||||
await stepContext.Context.SendActivityAsync("Maybe here I would start a dialog.", cancellationToken: cancellationToken);
|
||||
var invokeResponseNoEoc = new InvokeResponse { Status = (int)HttpStatusCode.OK, Body = JObject.Parse("{ \"origin\": \"New York\", \"destination\": \"Seattle\"}") };
|
||||
await stepContext.Context.SendActivityAsync(new Activity { Value = invokeResponseNoEoc, Type = ActivityTypesEx.InvokeResponse }, cancellationToken).ConfigureAwait(false);
|
||||
return EndOfTurn;
|
||||
|
||||
case "InvokeWithEoc":
|
||||
var invokeResponse = new InvokeResponse { Status = (int)HttpStatusCode.OK, Body = JObject.Parse("{ \"origin\": \"New York\", \"destination\": \"Seattle\"}") };
|
||||
await stepContext.Context.SendActivityAsync(new Activity { Value = invokeResponse, Type = ActivityTypesEx.InvokeResponse }, cancellationToken).ConfigureAwait(false);
|
||||
return new DialogTurnResult(DialogTurnStatus.Complete);
|
||||
|
||||
default:
|
||||
// We didn't get an event name we can handle.
|
||||
await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Unrecognized InvokeName: \"{activity.Name}\".", inputHint: InputHints.IgnoringInput), cancellationToken);
|
||||
return new DialogTurnResult(DialogTurnStatus.Complete);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<DialogTurnResult> BeginGetWeather(WaterfallStepContext stepContext, CancellationToken cancellationToken)
|
||||
{
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
"dialogEchoSkillBot": {
|
||||
"description": "Calls the Echo Skill Bot from a skill",
|
||||
"type": "event",
|
||||
"name": "EchoSkillBot"
|
||||
"name": "EchoSkill"
|
||||
},
|
||||
"getWeather": {
|
||||
"description": "Retrieves and returns the weather for the user's location.",
|
|
@ -1,74 +0,0 @@
|
|||
# SkillDialog
|
||||
|
||||
Bot Framework v4 Skills with Dialogs sample.
|
||||
|
||||
This bot has been created using the [Bot Framework](https://dev.botframework.com); it shows how to use a skill dialog from a root bot.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [.NET Core SDK](https://dotnet.microsoft.com/download) version 3.1
|
||||
|
||||
```bash
|
||||
# determine dotnet version
|
||||
dotnet --version
|
||||
```
|
||||
|
||||
## Key concepts in this sample
|
||||
|
||||
The solution uses dialogs, within both a parent bot (`DialogRootBot`) and a skill bot (`DialogSkillBot`).
|
||||
It demonstrates how to post activities from the parent bot to the skill bot and return the skill responses to the user.
|
||||
|
||||
- `DialogRootBot`: this project shows how to consume a skill bot using a `SkillDialog`. It includes:
|
||||
- A [root dialog](DialogRootBot/Dialogs/MainDialog.cs) that can call different actions on a skill using a `SkillDialog`:
|
||||
- To send events activities.
|
||||
- To send message activities.
|
||||
- To cancel a `SkillDialog` using `CancelAllDialogsAsync` that automatically sends an `EndOfConversation` activity to remotely let a skill know that it needs to end a conversation.
|
||||
- A sample [AdapterWithErrorHandler](DialogRootBot/AdapterWithErrorHandler.cs) adapter that shows how to handle errors, terminate skills and send traces back to the emulator to help debugging the bot.
|
||||
- A sample [AllowedSkillsClaimsValidator](DialogRootBot/Authentication/AllowedSkillsClaimsValidator.cs) class that shows how to validate that responses sent to the bot are coming from the configured skills.
|
||||
- A [Logger Middleware](DialogRootBot/Middleware/LoggerMiddleware.cs) that shows how to handle and log activities coming from a skill.
|
||||
- A [SkillConversationIdFactory](DialogRootBot/SkillConversationIdFactory.cs) based on `IStorage` used to create and maintain conversation IDs to interact with a skill.
|
||||
- A [SkillsConfiguration](DialogRootBot/SkillsConfiguration.cs) class that can load skill definitions from the appsettings.json file.
|
||||
- A [startup](DialogRootBot/Startup.cs) class that shows how to register the different root bot components for dependency injection.
|
||||
- A [SkillController](DialogRootBot/Controllers/SkillController.cs) that handles skill responses.
|
||||
|
||||
- `DialogSkillBot`: this project shows a modified CoreBot that acts as a skill. It receives event and message activities from the parent bot and executes the requested tasks. This project includes:
|
||||
- An [ActivityRouterDialog](DialogSkillBot/Dialogs/ActivityRouterDialog.cs) that handles Event and Message activities coming from a parent and performs different tasks.
|
||||
- Event activities are routed to specific dialogs using the parameters provided in the `Values` property of the activity.
|
||||
- Message activities are sent to LUIS if configured and trigger the desired tasks if the intent is recognized.
|
||||
- A sample [ActivityHandler](DialogSkillBot/Bots/SkillBot.cs) that uses the `RunAsync` method on `ActivityRouterDialog`.
|
||||
|
||||
Note: Starting in Bot Framework 4.8, the `RunAsync` method adds support to automatically send `EndOfConversation` with return values when the bot is running as a skill and the current dialog ends. It also handles reprompt messages to resume a skill where it left of.
|
||||
- A sample [SkillAdapterWithErrorHandler](DialogSkillBot/SkillAdapterWithErrorHandler.cs) adapter that shows how to handle errors, terminate the skills, send traces back to the emulator to help debugging the bot and send `EndOfConversation` messages to the parent bot with details of the error.
|
||||
- A sample [AllowedCallersClaimsValidator](DialogSkillBot/Authentication/AllowedCallersClaimsValidator.cs) that shows how to validate that the skill is only invoked from a list of allowed callers
|
||||
- A [startup](DialogSkillBot/Startup.cs) class that shows how to register the different skill components for dependency injection.
|
||||
- A [sample skill manifest](DialogSkillBot/wwwroot/manifest/dialogchildbot-manifest-1.0.json) that describes what the skill can do.
|
||||
|
||||
## To try this sample
|
||||
|
||||
- Clone the repository.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/microsoft/botbuilder-samples.git
|
||||
```
|
||||
|
||||
- Create a bot registration in the azure portal for the `DialogSkillBot` and update [DialogSkillBot/appsettings.json](DialogSkillBot/appsettings.json) with the AppId and password.
|
||||
- Create a bot registration in the azure portal for the DialogRootBot and update [DialogRootBot/appsettings.json](DialogRootBot/appsettings.json) with the AppId and password.
|
||||
- Update the BotFrameworkSkills section in [DialogRootBot/appsettings.json](DialogRootBot/appsettings.json) with the AppId for the skill you created in the previous step.
|
||||
- (Optional) Configure the LuisAppId, LuisAPIKey and LuisAPIHostName section in the [DialogSkillBot/appsettings.json](DialogSkillBot/appsettings.json) if you want to run message activities through LUIS.
|
||||
- Open the `Microsoft.Bot.Builder.Skills.sln` solution and configure it to [start debugging with multiple processes](https://docs.microsoft.com/en-us/visualstudio/debugger/debug-multiple-processes?view=vs-2019#start-debugging-with-multiple-processes).
|
||||
|
||||
## Testing the bot using Bot Framework Emulator
|
||||
|
||||
[Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel.
|
||||
|
||||
- Install the Bot Framework Emulator version 4.8.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases)
|
||||
|
||||
### Connect to the bot using Bot Framework Emulator
|
||||
|
||||
- Launch Bot Framework Emulator
|
||||
- File -> Open Bot
|
||||
- Enter a Bot URL of `http://localhost:3978/api/messages`, the `MicrosoftAppId` and `MicrosoftAppPassword` for the `DialogRootBot`
|
||||
|
||||
## Deploy the bots to Azure
|
||||
|
||||
To learn more about deploying a bot to Azure, see [Deploy your bot to Azure](https://aka.ms/azuredeployment) for a complete list of deployment instructions.
|
|
@ -0,0 +1,55 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Generated with Bot Builder V4 SDK Template for Visual Studio EchoSkillBot v4.7.0
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Connector.Authentication;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.TeamsSkillBot.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// Sample claims validator that loads an allowed list from configuration if present
|
||||
/// and checks that requests are coming from allowed parent bots.
|
||||
/// </summary>
|
||||
public class AllowedCallersClaimsValidator : ClaimsValidator
|
||||
{
|
||||
private const string ConfigKey = "AllowedCallers";
|
||||
private readonly List<string> _allowedCallers;
|
||||
|
||||
public AllowedCallersClaimsValidator(IConfiguration config)
|
||||
{
|
||||
if (config == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(config));
|
||||
}
|
||||
|
||||
// AllowedCallers is the setting in appsettings.json file
|
||||
// that consists of the list of parent bot ids that are allowed to access the skill
|
||||
// to add a new parent bot simply go to the AllowedCallers and add
|
||||
// the parent bot's microsoft app id to the list
|
||||
var section = config.GetSection(ConfigKey);
|
||||
var appsList = section.Get<string[]>();
|
||||
_allowedCallers = appsList != null ? new List<string>(appsList) : null;
|
||||
}
|
||||
|
||||
public override Task ValidateClaimsAsync(IList<Claim> claims)
|
||||
{
|
||||
// if _allowedCallers is null we allow all calls
|
||||
if (_allowedCallers != null && _allowedCallers.Count > 0 && SkillValidation.IsSkillClaim(claims))
|
||||
{
|
||||
// Check that the appId claim in the skill request is in the list of skills configured for this bot.
|
||||
var appId = JwtTokenValidation.GetAppIdFromClaims(claims);
|
||||
if (!_allowedCallers.Contains(appId))
|
||||
{
|
||||
throw new UnauthorizedAccessException($"Received a request from a bot with an app ID of \"{appId}\". To enable requests from this caller, add the app ID to your configuration file.");
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,162 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AdaptiveCards;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Teams;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Microsoft.Bot.Schema.Teams;
|
||||
using Microsoft.BotBuilderSamples.TeamsSkillBot.Extensions;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.TeamsSkillBot.Bots
|
||||
{
|
||||
public class TeamsBot : TeamsActivityHandler
|
||||
{
|
||||
protected override async Task<InvokeResponse> OnInvokeActivityAsync(ITurnContext<IInvokeActivity> turnContext, CancellationToken cancellationToken)
|
||||
{
|
||||
// Handle invoke triggers for the skill.
|
||||
switch (turnContext.Activity.Name)
|
||||
{
|
||||
case "TeamsTaskModule":
|
||||
{
|
||||
var reply = MessageFactory.Attachment(GetTaskModuleHeroCard());
|
||||
await turnContext.SendActivityAsync(reply, cancellationToken);
|
||||
|
||||
//var token = await (turnContext.Adapter as SkillAdapterWithErrorHandler).GetBotSkillToken(turnContext);
|
||||
|
||||
return new InvokeResponse
|
||||
{
|
||||
Status = (int)HttpStatusCode.OK,
|
||||
};
|
||||
}
|
||||
|
||||
case "TeamsCardAction":
|
||||
{
|
||||
var reply = MessageFactory.Attachment(GetAdaptiveCardWithInvokeAction());
|
||||
await turnContext.SendActivityAsync(reply, cancellationToken);
|
||||
|
||||
return new InvokeResponse
|
||||
{
|
||||
Status = (int)HttpStatusCode.OK,
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
// Let the base handle it.
|
||||
return await base.OnInvokeActivityAsync(turnContext, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
|
||||
{
|
||||
var reply = MessageFactory.Attachment(GetTaskModuleHeroCard());
|
||||
await turnContext.SendActivityAsync(reply, cancellationToken);
|
||||
}
|
||||
|
||||
protected override async Task<TaskModuleResponse> OnTeamsTaskModuleFetchAsync(ITurnContext<IInvokeActivity> turnContext, TaskModuleRequest taskModuleRequest, CancellationToken cancellationToken)
|
||||
{
|
||||
var reply = MessageFactory.Text("OnTeamsTaskModuleFetchAsync TaskModuleRequest: " + JsonConvert.SerializeObject(taskModuleRequest));
|
||||
|
||||
await turnContext.SendActivityAsync(reply, cancellationToken);
|
||||
|
||||
return new TaskModuleResponse
|
||||
{
|
||||
Task = new TaskModuleContinueResponse
|
||||
{
|
||||
Value = new TaskModuleTaskInfo
|
||||
{
|
||||
Card = CreateAdaptiveCardAttachment(),
|
||||
Height = 200,
|
||||
Width = 400,
|
||||
Title = "Adaptive Card: Inputs",
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected override async Task<TaskModuleResponse> OnTeamsTaskModuleSubmitAsync(ITurnContext<IInvokeActivity> turnContext, TaskModuleRequest taskModuleRequest, CancellationToken cancellationToken)
|
||||
{
|
||||
var reply = MessageFactory.Text("OnTeamsTaskModuleSubmitAsync Value: " + JsonConvert.SerializeObject(taskModuleRequest));
|
||||
await turnContext.SendActivityAsync(reply, cancellationToken);
|
||||
|
||||
// Send End of conversation at the end.
|
||||
var activity = new Activity(ActivityTypes.EndOfConversation)
|
||||
{
|
||||
Value = taskModuleRequest.Data,
|
||||
Locale = ((Activity)turnContext.Activity).Locale
|
||||
};
|
||||
await turnContext.SendActivityAsync(activity, cancellationToken);
|
||||
|
||||
return new TaskModuleResponse
|
||||
{
|
||||
Task = new TaskModuleMessageResponse
|
||||
{
|
||||
Value = "Thanks!",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected override async Task<InvokeResponse> OnTeamsCardActionInvokeAsync(ITurnContext<IInvokeActivity> turnContext, CancellationToken cancellationToken)
|
||||
{
|
||||
await turnContext.SendActivityAsync(MessageFactory.Text("hello from OnTeamsCardActionInvokeAsync."), cancellationToken);
|
||||
|
||||
// Send End of conversation at the end.
|
||||
var activity = new Activity(ActivityTypes.EndOfConversation)
|
||||
{
|
||||
Locale = ((Activity)turnContext.Activity).Locale
|
||||
};
|
||||
await turnContext.SendActivityAsync(activity, cancellationToken);
|
||||
|
||||
return new InvokeResponse { Status = (int)HttpStatusCode.OK };
|
||||
}
|
||||
|
||||
private Attachment GetTaskModuleHeroCard()
|
||||
{
|
||||
return new HeroCard
|
||||
{
|
||||
Title = "Task Module Invocation from Hero Card",
|
||||
Subtitle = "This is a hero card with a Task Module Action button. Click the button to show an Adaptive Card within a Task Module.",
|
||||
Buttons = new List<CardAction>
|
||||
{
|
||||
new TaskModuleAction("Adaptive Card", new { data = "adaptivecard" }),
|
||||
},
|
||||
}.ToAttachment();
|
||||
}
|
||||
|
||||
private Attachment GetAdaptiveCardWithInvokeAction()
|
||||
{
|
||||
var adaptiveCard = new AdaptiveCard();
|
||||
adaptiveCard.Body.Add(new AdaptiveTextBlock("Bot Builder Invoke Action"));
|
||||
var action4 = new CardAction("invoke", "invoke", null, null, null, JObject.Parse(@"{ ""key"" : ""value"" }"));
|
||||
adaptiveCard.Actions.Add(action4.ToAdaptiveCardAction());
|
||||
|
||||
return adaptiveCard.ToAttachment();
|
||||
}
|
||||
|
||||
private Attachment CreateAdaptiveCardAttachment()
|
||||
{
|
||||
// combine path for cross platform support
|
||||
string[] paths =
|
||||
{
|
||||
".",
|
||||
"Resources",
|
||||
"adaptiveCard.json"
|
||||
};
|
||||
var adaptiveCardJson = File.ReadAllText(Path.Combine(paths));
|
||||
|
||||
var adaptiveCardAttachment = new Attachment
|
||||
{
|
||||
ContentType = "application/vnd.microsoft.card.adaptive",
|
||||
Content = JsonConvert.DeserializeObject(adaptiveCardJson),
|
||||
};
|
||||
return adaptiveCardAttachment;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.TeamsSkillBot.Controllers
|
||||
{
|
||||
// This ASP Controller is created to handle a request. Dependency Injection will provide the Adapter and IBot
|
||||
// implementation at runtime. Multiple different IBot implementations running at different endpoints can be
|
||||
// achieved by specifying a more specific type for the bot constructor argument.
|
||||
[Route("api/messages")]
|
||||
[ApiController]
|
||||
public class BotController : ControllerBase
|
||||
{
|
||||
private readonly IBotFrameworkHttpAdapter _adapter;
|
||||
private readonly IBot _bot;
|
||||
|
||||
public BotController(IBotFrameworkHttpAdapter adapter, IBot bot)
|
||||
{
|
||||
_adapter = adapter;
|
||||
_bot = bot;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[HttpGet]
|
||||
public async Task PostAsync()
|
||||
{
|
||||
// Delegate the processing of the HTTP POST to the adapter.
|
||||
// The adapter will invoke the bot.
|
||||
await _adapter.ProcessAsync(Request, Response, _bot);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using AdaptiveCards;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.TeamsSkillBot.Extensions
|
||||
{
|
||||
//https://github.com/microsoft/botbuilder-dotnet/blob/master/tests/Teams/AdaptiveCards/AdaptiveCardExtensions.cs
|
||||
public static class AdaptiveCardExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new attachment from AdaptiveCard.
|
||||
/// </summary>
|
||||
/// <param name="card"> The instance of AdaptiveCard.</param>
|
||||
/// <returns> The generated attachment.</returns>
|
||||
public static Attachment ToAttachment(this AdaptiveCard card)
|
||||
{
|
||||
return new Attachment
|
||||
{
|
||||
Content = card,
|
||||
ContentType = AdaptiveCard.ContentType,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrap BotBuilder action into AdaptiveCard submit action.
|
||||
/// </summary>
|
||||
/// <param name="action"> The instance of adaptive card submit action.</param>
|
||||
/// <param name="targetAction"> Target action to be adapted.</param>
|
||||
public static void RepresentAsBotBuilderAction(this AdaptiveSubmitAction action, CardAction targetAction)
|
||||
{
|
||||
var wrappedAction = new CardAction
|
||||
{
|
||||
Type = targetAction.Type,
|
||||
Value = targetAction.Value,
|
||||
Text = targetAction.Text,
|
||||
DisplayText = targetAction.DisplayText,
|
||||
};
|
||||
|
||||
var serializerSettings = new JsonSerializerSettings();
|
||||
serializerSettings.NullValueHandling = NullValueHandling.Ignore;
|
||||
|
||||
var jsonStr = action.DataJson == null ? "{}" : action.DataJson;
|
||||
JToken dataJson = JObject.Parse(jsonStr);
|
||||
dataJson["msteams"] = JObject.FromObject(wrappedAction, JsonSerializer.Create(serializerSettings));
|
||||
|
||||
action.Title = targetAction.Title;
|
||||
action.DataJson = dataJson.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrap BotBuilder action into AdaptiveCard submit action.
|
||||
/// </summary>
|
||||
/// <param name="action"> Target bot builder aciton to be adapted.</param>
|
||||
/// <returns> The wrapped adaptive card submit action.</returns>
|
||||
public static AdaptiveSubmitAction ToAdaptiveCardAction(this CardAction action)
|
||||
{
|
||||
var adaptiveCardAction = new AdaptiveSubmitAction();
|
||||
adaptiveCardAction.RepresentAsBotBuilderAction(action);
|
||||
return adaptiveCardAction;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
// Generated with Bot Builder V4 SDK Template for Visual Studio EchoSkillBot v4.7.0
|
||||
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.TeamsSkillBot
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CreateHostBuilder(args).Build().Run();
|
||||
}
|
||||
|
||||
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||
Host.CreateDefaultBuilder(args)
|
||||
.ConfigureWebHostDefaults(webBuilder =>
|
||||
{
|
||||
webBuilder.ConfigureLogging((logging) =>
|
||||
{
|
||||
logging.AddDebug();
|
||||
logging.AddConsole();
|
||||
});
|
||||
webBuilder.UseStartup<Startup>();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
{
|
||||
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
|
||||
"version": "1.0",
|
||||
"type": "AdaptiveCard",
|
||||
"body": [
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"text": "Enter Text Here",
|
||||
"weight": "bolder",
|
||||
"isSubtle": false
|
||||
},
|
||||
{
|
||||
"type": "Input.Text",
|
||||
"id": "usertext",
|
||||
"spacing": "none",
|
||||
"isMultiLine": "true",
|
||||
"placeholder": "add some text and submit"
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"type": "Action.Submit",
|
||||
"title": "Submit"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
// 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.Integration.AspNet.Core;
|
||||
using Microsoft.Bot.Builder.TraceExtensions;
|
||||
using Microsoft.Bot.Connector.Authentication;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.TeamsSkillBot
|
||||
{
|
||||
public class SkillAdapterWithErrorHandler : BotFrameworkHttpAdapter
|
||||
{
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
public SkillAdapterWithErrorHandler(IConfiguration configuration, ICredentialProvider credentialProvider, AuthenticationConfiguration authConfig, ILogger<BotFrameworkHttpAdapter> logger, ConversationState conversationState = null)
|
||||
: base(configuration, credentialProvider, authConfig, logger: logger)
|
||||
{
|
||||
_configuration = configuration;
|
||||
OnTurnError = async (turnContext, exception) =>
|
||||
{
|
||||
// Log any leaked exception from the application.
|
||||
logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}");
|
||||
|
||||
// Send a message to the user
|
||||
var errorMessageText = "The skill encountered an error or bug.";
|
||||
var errorMessage = MessageFactory.Text(errorMessageText, errorMessageText, InputHints.IgnoringInput);
|
||||
await turnContext.SendActivityAsync(errorMessage);
|
||||
|
||||
errorMessageText = "To continue to run this bot, please fix the bot source code.";
|
||||
errorMessage = MessageFactory.Text(errorMessageText, errorMessageText, InputHints.ExpectingInput);
|
||||
await turnContext.SendActivityAsync(errorMessage);
|
||||
|
||||
// Send a trace activity, which will be displayed in the Bot Framework Emulator
|
||||
// Note: we return the entire exception in the value property to help the developer, this should not be done in prod.
|
||||
await turnContext.TraceActivityAsync("OnTurnError Trace", exception.ToString(), "https://www.botframework.com/schemas/error", "TurnError");
|
||||
|
||||
// Send and EndOfConversation activity to the skill caller with the error to end the conversation
|
||||
// and let the caller decide what to do.
|
||||
var endOfConversation = Activity.CreateEndOfConversationActivity();
|
||||
endOfConversation.Code = "SkillError";
|
||||
endOfConversation.Text = exception.Message;
|
||||
await turnContext.SendActivityAsync(endOfConversation);
|
||||
|
||||
if (conversationState != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Delete the conversationState for the current conversation to prevent the
|
||||
// bot from getting stuck in a error-loop caused by being in a bad state.
|
||||
// ConversationState should be thought of as similar to "cookie-state" in a Web pages.
|
||||
await conversationState.DeleteAsync(turnContext);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, $"Exception caught on attempting to Delete ConversationState : {ex}");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<string> GetBotSkillToken(ITurnContext turnContext)
|
||||
{
|
||||
var appId = _configuration.GetSection(MicrosoftAppCredentials.MicrosoftAppIdKey)?.Value;
|
||||
var oAuthScope = turnContext.TurnState.Get<string>(OAuthScopeKey);
|
||||
var token = await (await GetAppCredentialsAsync(appId, oAuthScope).ConfigureAwait(false)).GetTokenAsync();
|
||||
return token;
|
||||
}
|
||||
|
||||
private async Task<AppCredentials> GetAppCredentialsAsync(string appId, string oAuthScope, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (appId == null)
|
||||
{
|
||||
return MicrosoftAppCredentials.Empty;
|
||||
}
|
||||
|
||||
var cacheKey = $"{appId}{oAuthScope}";
|
||||
if (AppCredentialMap.TryGetValue(cacheKey, out var appCredentials))
|
||||
{
|
||||
return appCredentials;
|
||||
}
|
||||
|
||||
// Credentials not found in cache, build them
|
||||
appCredentials = await BuildCredentialsAsync(appId, oAuthScope).ConfigureAwait(false);
|
||||
|
||||
// Cache the credentials for later use
|
||||
AppCredentialMap[cacheKey] = appCredentials;
|
||||
return appCredentials;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.BotFramework;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.Bot.Connector.Authentication;
|
||||
using Microsoft.BotBuilderSamples.TeamsSkillBot.Authentication;
|
||||
using Microsoft.BotBuilderSamples.TeamsSkillBot.Bots;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.TeamsSkillBot
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
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.AddControllers().AddNewtonsoftJson();
|
||||
|
||||
// Configure credentials
|
||||
services.AddSingleton<ICredentialProvider, ConfigurationCredentialProvider>();
|
||||
|
||||
// Register AuthConfiguration to enable custom claim validation.
|
||||
services.AddSingleton(sp => new AuthenticationConfiguration { ClaimsValidator = new AllowedCallersClaimsValidator(sp.GetService<IConfiguration>()) });
|
||||
|
||||
// Create the Bot Framework Adapter with error handling enabled.
|
||||
services.AddSingleton<IBotFrameworkHttpAdapter, SkillAdapterWithErrorHandler>();
|
||||
|
||||
// Create the bot as a transient. In this case the ASP Controller is expecting an IBot.
|
||||
services.AddTransient<IBot, TeamsBot>();
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.UseDefaultFiles()
|
||||
.UseStaticFiles()
|
||||
.UseWebSockets()
|
||||
.UseRouting()
|
||||
.UseAuthorization()
|
||||
.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllers();
|
||||
});
|
||||
|
||||
// app.UseHttpsRedirection();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<AssemblyName>Microsoft.BotBuilderSamples.TeamsSkillBot</AssemblyName>
|
||||
<RootNamespace>Microsoft.BotBuilderSamples.TeamsSkillBot</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AdaptiveCards" Version="1.1.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.0" />
|
||||
<ProjectReference Include="..\..\..\..\libraries\integration\Microsoft.Bot.Builder.Integration.AspNet.Core\Microsoft.Bot.Builder.Integration.AspNet.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Update="appsettings.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"MicrosoftAppId": "",
|
||||
"MicrosoftAppPassword": "",
|
||||
"AllowedCallers": []
|
||||
}
|
||||
|
|
@ -0,0 +1,420 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>TeamsSkillBot</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
font-family: Segoe UI;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
header {
|
||||
background-image: url("data:image/svg+xml,%3Csvg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 4638.9 651.6' style='enable-background:new 0 0 4638.9 651.6;' xml:space='preserve'%3E%3Cstyle type='text/css'%3E .st0%7Bfill:%2355A0E0;%7D .st1%7Bfill:none;%7D .st2%7Bfill:%230058A8;%7D .st3%7Bfill:%23328BD8;%7D .st4%7Bfill:%23B6DCF1;%7D .st5%7Bopacity:0.2;fill:url(%23SVGID_1_);enable-background:new ;%7D%0A%3C/style%3E%3Crect y='1.1' class='st0' width='4640' height='646.3'/%3E%3Cpath class='st1' d='M3987.8,323.6L4310.3,1.1h-65.6l-460.1,460.1c-17.5,17.5-46.1,17.5-63.6,0L3260.9,1.1H0v646.3h3660.3 L3889,418.7c17.5-17.5,46.1-17.5,63.6,0l228.7,228.7h66.6l-260.2-260.2C3970.3,369.8,3970.3,341.1,3987.8,323.6z'/%3E%3Cpath class='st2' d='M3784.6,461.2L4244.7,1.1h-983.9l460.1,460.1C3738.4,478.7,3767.1,478.7,3784.6,461.2z'/%3E%3Cpath class='st3' d='M4640,1.1h-329.8l-322.5,322.5c-17.5,17.5-17.5,46.1,0,63.6l260.2,260.2H4640L4640,1.1L4640,1.1z'/%3E%3Cpath class='st4' d='M3889,418.8l-228.7,228.7h521.1l-228.7-228.7C3935.2,401.3,3906.5,401.3,3889,418.8z'/%3E%3ClinearGradient id='SVGID_1_' gradientUnits='userSpaceOnUse' x1='3713.7576' y1='438.1175' x2='3911.4084' y2='14.2535' gradientTransform='matrix(1 0 0 -1 0 641.3969)'%3E%3Cstop offset='0' style='stop-color:%23FFFFFF;stop-opacity:0.5'/%3E%3Cstop offset='1' style='stop-color:%23FFFFFF'/%3E%3C/linearGradient%3E%3Cpath class='st5' d='M3952.7,124.5c-17.5-17.5-46.1-17.5-63.6,0l-523,523h1109.6L3952.7,124.5z'/%3E%3C/svg%3E%0A");
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100%;
|
||||
background-position: right;
|
||||
background-color: #55A0E0;
|
||||
width: 100%;
|
||||
font-size: 44px;
|
||||
height: 120px;
|
||||
color: white;
|
||||
padding: 30px 0 40px 0px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
background-image: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20xmlns%3Axlink%3D%22http%3A//www.w3.org/1999/xlink%22%20x%3D%220px%22%20y%3D%220px%22%0A%09%20viewBox%3D%220%200%20150.2%20125%22%20style%3D%22enable-background%3Anew%200%200%20150.2%20125%3B%22%20xml%3Aspace%3D%22preserve%22%3E%0A%3Cstyle%20type%3D%22text/css%22%3E%0A%09.st0%7Bfill%3Anone%3B%7D%0A%09.st1%7Bfill%3A%23FFFFFF%3B%7D%0A%3C/style%3E%0A%3Crect%20x%3D%220.5%22%20class%3D%22st0%22%20width%3D%22149.7%22%20height%3D%22125%22/%3E%0A%3Cg%3E%0A%09%3Cpath%20class%3D%22st1%22%20d%3D%22M59%2C102.9L21.8%2C66c-3.5-3.5-3.5-9.1%2C0-12.5l37-36.5l2.9%2C3l-37%2C36.4c-1.8%2C1.8-1.8%2C4.7%2C0%2C6.6l37.2%2C37L59%2C102.9z%22%0A%09%09/%3E%0A%3C/g%3E%0A%3Cg%3E%0A%09%3Cpath%20class%3D%22st1%22%20d%3D%22M92.5%2C102.9l-3-3l37.2-37c0.9-0.9%2C1.4-2%2C1.4-3.3c0-1.2-0.5-2.4-1.4-3.3L89.5%2C20l2.9-3l37.2%2C36.4%0A%09%09c1.7%2C1.7%2C2.6%2C3.9%2C2.6%2C6.3s-0.9%2C4.6-2.6%2C6.3L92.5%2C102.9z%22/%3E%0A%3C/g%3E%0A%3Cg%3E%0A%09%3Cpath%20class%3D%22st1%22%20d%3D%22M90.1%2C68.4c-4.5%2C0-8-3.5-8-8.1c0-4.5%2C3.5-8.1%2C8-8.1c4.4%2C0%2C8%2C3.7%2C8%2C8.1C98.1%2C64.7%2C94.4%2C68.4%2C90.1%2C68.4z%0A%09%09%20M90.1%2C56.5c-2.2%2C0-3.8%2C1.7-3.8%2C3.9c0%2C2.2%2C1.7%2C3.9%2C3.8%2C3.9c1.9%2C0%2C3.8-1.6%2C3.8-3.9S91.9%2C56.5%2C90.1%2C56.5z%22/%3E%0A%3C/g%3E%0A%3Cg%3E%0A%09%3Cpath%20class%3D%22st1%22%20d%3D%22M61.4%2C68.4c-4.5%2C0-8-3.5-8-8.1c0-4.5%2C3.5-8.1%2C8-8.1c4.4%2C0%2C8%2C3.7%2C8%2C8.1C69.5%2C64.7%2C65.8%2C68.4%2C61.4%2C68.4z%0A%09%09%20M61.4%2C56.5c-2.2%2C0-3.8%2C1.7-3.8%2C3.9c0%2C2.2%2C1.7%2C3.9%2C3.8%2C3.9c1.9%2C0%2C3.8-1.6%2C3.8-3.9S63.3%2C56.5%2C61.4%2C56.5z%22/%3E%0A%3C/g%3E%0A%3C/svg%3E%0A");
|
||||
background-repeat: no-repeat;
|
||||
float: left;
|
||||
height: 140px;
|
||||
width: 140px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.header-text {
|
||||
padding-left: 1%;
|
||||
color: #FFFFFF;
|
||||
font-family: "Segoe UI";
|
||||
font-size: 72px;
|
||||
font-weight: 300;
|
||||
letter-spacing: 0.35px;
|
||||
line-height: 96px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.header-inner-container {
|
||||
min-width: 480px;
|
||||
max-width: 1366px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.header-inner-container::after {
|
||||
content: "";
|
||||
clear: both;
|
||||
display: table;
|
||||
}
|
||||
|
||||
.main-content-area {
|
||||
padding-left: 30px;
|
||||
}
|
||||
|
||||
.content-title {
|
||||
color: #000000;
|
||||
font-family: "Segoe UI";
|
||||
font-size: 46px;
|
||||
font-weight: 300;
|
||||
line-height: 62px;
|
||||
}
|
||||
|
||||
.main-text {
|
||||
color: #808080;
|
||||
font-size: 24px;
|
||||
font-family: "Segoe UI";
|
||||
font-size: 24px;
|
||||
font-weight: 200;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.main-text-p1 {
|
||||
padding-top: 48px;
|
||||
padding-bottom: 28px;
|
||||
}
|
||||
|
||||
.endpoint {
|
||||
height: 32px;
|
||||
width: 571px;
|
||||
color: #808080;
|
||||
font-family: "Segoe UI";
|
||||
font-size: 24px;
|
||||
font-weight: 200;
|
||||
line-height: 32px;
|
||||
padding-top: 28px;
|
||||
}
|
||||
|
||||
.how-to-build-section {
|
||||
padding-top: 20px;
|
||||
padding-left: 30px;
|
||||
}
|
||||
|
||||
.how-to-build-section>h3 {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.35px;
|
||||
line-height: 22px;
|
||||
margin: 0 0 24px 0;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.step-container {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.step-container dl {
|
||||
border-left: 1px solid #A0A0A0;
|
||||
display: block;
|
||||
padding: 0 24px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.step-container dl>dt::before {
|
||||
background-color: white;
|
||||
border: 1px solid #A0A0A0;
|
||||
border-radius: 100%;
|
||||
content: '';
|
||||
left: 47px;
|
||||
height: 11px;
|
||||
position: absolute;
|
||||
width: 11px;
|
||||
}
|
||||
|
||||
.step-container dl>.test-bullet::before {
|
||||
background-color: blue;
|
||||
}
|
||||
|
||||
.step-container dl>dt {
|
||||
display: block;
|
||||
font-size: inherit;
|
||||
font-weight: bold;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.step-container dl>dd {
|
||||
font-size: inherit;
|
||||
line-height: 20px;
|
||||
margin-left: 0;
|
||||
padding-bottom: 32px;
|
||||
}
|
||||
|
||||
.step-container:last-child dl {
|
||||
border-left: 1px solid transparent;
|
||||
}
|
||||
|
||||
.ctaLink {
|
||||
background-color: transparent;
|
||||
border: 1px solid transparent;
|
||||
color: #006AB1;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
padding: 0;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.ctaLink:focus {
|
||||
outline: 1px solid #00bcf2;
|
||||
}
|
||||
|
||||
.ctaLink:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.step-icon {
|
||||
display: flex;
|
||||
height: 38px;
|
||||
margin-right: 15px;
|
||||
width: 38px;
|
||||
}
|
||||
|
||||
.step-icon>div {
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.ms-logo-container {
|
||||
min-width: 580px;
|
||||
max-width: 980px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
left: 0;
|
||||
right: 0;
|
||||
transition: bottom 400ms;
|
||||
}
|
||||
|
||||
.ms-logo {
|
||||
float: right;
|
||||
background-image: url("data:image/svg+xml;utf8,%0A%3Csvg%20version%3D%221.1%22%20id%3D%22MS-symbol%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20xmlns%3Axlink%3D%22http%3A//www.w3.org/1999/xlink%22%20x%3D%220px%22%20y%3D%220px%22%0A%09%20viewBox%3D%220%200%20400%20120%22%20style%3D%22enable-background%3Anew%200%200%20400%20120%3B%22%20xml%3Aspace%3D%22preserve%22%3E%0A%3Cstyle%20type%3D%22text/css%22%3E%0A%09.st0%7Bfill%3Anone%3B%7D%0A%09.st1%7Bfill%3A%23737474%3B%7D%0A%09.st2%7Bfill%3A%23D63F26%3B%7D%0A%09.st3%7Bfill%3A%23167D3E%3B%7D%0A%09.st4%7Bfill%3A%232E76BC%3B%7D%0A%09.st5%7Bfill%3A%23FDB813%3B%7D%0A%3C/style%3E%0A%3Crect%20x%3D%220.6%22%20class%3D%22st0%22%20width%3D%22398.7%22%20height%3D%22119%22/%3E%0A%3Cpath%20class%3D%22st1%22%20d%3D%22M171.3%2C38.4v43.2h-7.5V47.7h-0.1l-13.4%2C33.9h-5l-13.7-33.9h-0.1v33.9h-6.9V38.4h10.8l12.4%2C32h0.2l13.1-32H171.3%0A%09z%20M177.6%2C41.7c0-1.2%2C0.4-2.2%2C1.3-3c0.9-0.8%2C1.9-1.2%2C3.1-1.2c1.3%2C0%2C2.4%2C0.4%2C3.2%2C1.3c0.8%2C0.8%2C1.3%2C1.8%2C1.3%2C3c0%2C1.2-0.4%2C2.2-1.3%2C3%0A%09c-0.9%2C0.8-1.9%2C1.2-3.2%2C1.2s-2.3-0.4-3.1-1.2C178%2C43.8%2C177.6%2C42.8%2C177.6%2C41.7z%20M185.7%2C50.6v31h-7.3v-31H185.7z%20M207.8%2C76.3%0A%09c1.1%2C0%2C2.3-0.3%2C3.6-0.8c1.3-0.5%2C2.5-1.2%2C3.6-2v6.8c-1.2%2C0.7-2.5%2C1.2-4%2C1.5c-1.5%2C0.3-3.1%2C0.5-4.9%2C0.5c-4.6%2C0-8.3-1.4-11.1-4.3%0A%09c-2.9-2.9-4.3-6.6-4.3-11c0-5%2C1.5-9.1%2C4.4-12.3c2.9-3.2%2C7-4.8%2C12.4-4.8c1.4%2C0%2C2.7%2C0.2%2C4.1%2C0.5c1.4%2C0.4%2C2.5%2C0.8%2C3.3%2C1.2v7%0A%09c-1.1-0.8-2.3-1.5-3.4-1.9c-1.2-0.5-2.4-0.7-3.6-0.7c-2.9%2C0-5.2%2C0.9-7%2C2.8c-1.8%2C1.9-2.7%2C4.4-2.7%2C7.6c0%2C3.1%2C0.8%2C5.6%2C2.5%2C7.3%0A%09C202.6%2C75.4%2C204.9%2C76.3%2C207.8%2C76.3z%20M235.7%2C50.1c0.6%2C0%2C1.1%2C0%2C1.6%2C0.1s0.9%2C0.2%2C1.2%2C0.3v7.4c-0.4-0.3-0.9-0.5-1.7-0.8%0A%09c-0.7-0.3-1.6-0.4-2.7-0.4c-1.8%2C0-3.3%2C0.8-4.5%2C2.3c-1.2%2C1.5-1.9%2C3.8-1.9%2C7v15.6h-7.3v-31h7.3v4.9h0.1c0.7-1.7%2C1.7-3%2C3-4%0A%09C232.2%2C50.6%2C233.8%2C50.1%2C235.7%2C50.1z%20M238.9%2C66.6c0-5.1%2C1.4-9.2%2C4.3-12.2c2.9-3%2C6.9-4.5%2C12.1-4.5c4.8%2C0%2C8.6%2C1.4%2C11.3%2C4.3%0A%09c2.7%2C2.9%2C4.1%2C6.8%2C4.1%2C11.7c0%2C5-1.4%2C9-4.3%2C12c-2.9%2C3-6.8%2C4.5-11.8%2C4.5c-4.8%2C0-8.6-1.4-11.4-4.2C240.3%2C75.3%2C238.9%2C71.4%2C238.9%2C66.6z%0A%09%20M246.5%2C66.3c0%2C3.2%2C0.7%2C5.7%2C2.2%2C7.4c1.5%2C1.7%2C3.6%2C2.6%2C6.3%2C2.6c2.7%2C0%2C4.7-0.9%2C6.1-2.6c1.4-1.7%2C2.1-4.2%2C2.1-7.6c0-3.3-0.7-5.8-2.2-7.5%0A%09c-1.4-1.7-3.4-2.5-6-2.5c-2.7%2C0-4.7%2C0.9-6.2%2C2.7C247.2%2C60.5%2C246.5%2C63%2C246.5%2C66.3z%20M281.5%2C58.8c0%2C1%2C0.3%2C1.9%2C1%2C2.5%0A%09c0.7%2C0.6%2C2.1%2C1.3%2C4.4%2C2.2c2.9%2C1.2%2C5%2C2.5%2C6.1%2C3.9c1.2%2C1.5%2C1.8%2C3.2%2C1.8%2C5.3c0%2C2.9-1.1%2C5.3-3.4%2C7c-2.2%2C1.8-5.3%2C2.7-9.1%2C2.7%0A%09c-1.3%2C0-2.7-0.2-4.3-0.5c-1.6-0.3-2.9-0.7-4-1.2v-7.2c1.3%2C0.9%2C2.8%2C1.7%2C4.3%2C2.2c1.5%2C0.5%2C2.9%2C0.8%2C4.2%2C0.8c1.6%2C0%2C2.9-0.2%2C3.6-0.7%0A%09c0.8-0.5%2C1.2-1.2%2C1.2-2.3c0-1-0.4-1.9-1.2-2.5c-0.8-0.7-2.4-1.5-4.6-2.4c-2.7-1.1-4.6-2.4-5.7-3.8c-1.1-1.4-1.7-3.2-1.7-5.4%0A%09c0-2.8%2C1.1-5.1%2C3.3-6.9c2.2-1.8%2C5.1-2.7%2C8.6-2.7c1.1%2C0%2C2.3%2C0.1%2C3.6%2C0.4c1.3%2C0.2%2C2.5%2C0.6%2C3.4%2C0.9v6.9c-1-0.6-2.1-1.2-3.4-1.7%0A%09c-1.3-0.5-2.6-0.7-3.8-0.7c-1.4%2C0-2.5%2C0.3-3.2%2C0.8C281.9%2C57.1%2C281.5%2C57.8%2C281.5%2C58.8z%20M297.9%2C66.6c0-5.1%2C1.4-9.2%2C4.3-12.2%0A%09c2.9-3%2C6.9-4.5%2C12.1-4.5c4.8%2C0%2C8.6%2C1.4%2C11.3%2C4.3c2.7%2C2.9%2C4.1%2C6.8%2C4.1%2C11.7c0%2C5-1.4%2C9-4.3%2C12c-2.9%2C3-6.8%2C4.5-11.8%2C4.5%0A%09c-4.8%2C0-8.6-1.4-11.4-4.2C299.4%2C75.3%2C297.9%2C71.4%2C297.9%2C66.6z%20M305.5%2C66.3c0%2C3.2%2C0.7%2C5.7%2C2.2%2C7.4c1.5%2C1.7%2C3.6%2C2.6%2C6.3%2C2.6%0A%09c2.7%2C0%2C4.7-0.9%2C6.1-2.6c1.4-1.7%2C2.1-4.2%2C2.1-7.6c0-3.3-0.7-5.8-2.2-7.5c-1.4-1.7-3.4-2.5-6-2.5c-2.7%2C0-4.7%2C0.9-6.2%2C2.7%0A%09C306.3%2C60.5%2C305.5%2C63%2C305.5%2C66.3z%20M353.9%2C56.6h-10.9v25h-7.4v-25h-5.2v-6h5.2v-4.3c0-3.3%2C1.1-5.9%2C3.2-8c2.1-2.1%2C4.8-3.1%2C8.1-3.1%0A%09c0.9%2C0%2C1.7%2C0%2C2.4%2C0.1c0.7%2C0.1%2C1.3%2C0.2%2C1.8%2C0.4V42c-0.2-0.1-0.7-0.3-1.3-0.5c-0.6-0.2-1.3-0.3-2.1-0.3c-1.5%2C0-2.7%2C0.5-3.5%2C1.4%0A%09s-1.2%2C2.4-1.2%2C4.2v3.7h10.9v-7l7.3-2.2v9.2h7.4v6h-7.4v14.5c0%2C1.9%2C0.3%2C3.3%2C1%2C4c0.7%2C0.8%2C1.8%2C1.2%2C3.3%2C1.2c0.4%2C0%2C0.9-0.1%2C1.5-0.3%0A%09c0.6-0.2%2C1.1-0.4%2C1.6-0.7v6c-0.5%2C0.3-1.2%2C0.5-2.3%2C0.7c-1.1%2C0.2-2.1%2C0.3-3.2%2C0.3c-3.1%2C0-5.4-0.8-6.9-2.5c-1.5-1.6-2.3-4.1-2.3-7.4%0A%09V56.6z%22/%3E%0A%3Cg%3E%0A%09%3Crect%20x%3D%2231%22%20y%3D%2224%22%20class%3D%22st2%22%20width%3D%2234.2%22%20height%3D%2234.2%22/%3E%0A%09%3Crect%20x%3D%2268.8%22%20y%3D%2224%22%20class%3D%22st3%22%20width%3D%2234.2%22%20height%3D%2234.2%22/%3E%0A%09%3Crect%20x%3D%2231%22%20y%3D%2261.8%22%20class%3D%22st4%22%20width%3D%2234.2%22%20height%3D%2234.2%22/%3E%0A%09%3Crect%20x%3D%2268.8%22%20y%3D%2261.8%22%20class%3D%22st5%22%20width%3D%2234.2%22%20height%3D%2234.2%22/%3E%0A%3C/g%3E%0A%3C/svg%3E%0A");
|
||||
}
|
||||
|
||||
.ms-logo-container>div {
|
||||
min-height: 60px;
|
||||
width: 150px;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.row {
|
||||
padding: 90px 0px 0 20px;
|
||||
min-width: 480px;
|
||||
max-width: 1366px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.column {
|
||||
float: left;
|
||||
width: 45%;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.row:after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.download-the-emulator {
|
||||
height: 20px;
|
||||
color: #0063B1;
|
||||
font-size: 15px;
|
||||
line-height: 20px;
|
||||
padding-bottom: 70px;
|
||||
}
|
||||
|
||||
.how-to-iframe {
|
||||
max-width: 700px !important;
|
||||
min-width: 650px !important;
|
||||
height: 700px !important;
|
||||
}
|
||||
|
||||
.remove-frame-height {
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1300px) {
|
||||
.ms-logo {
|
||||
padding-top: 30px;
|
||||
}
|
||||
|
||||
.header-text {
|
||||
font-size: 40x;
|
||||
}
|
||||
|
||||
.column {
|
||||
float: none;
|
||||
padding-top: 30px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ms-logo-container {
|
||||
padding-top: 30px;
|
||||
min-width: 480px;
|
||||
max-width: 650px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.row {
|
||||
padding: 20px 0px 0 20px;
|
||||
min-width: 480px;
|
||||
max-width: 650px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1370px) {
|
||||
header {
|
||||
background-color: #55A0E0;
|
||||
background-size: auto 200px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1230px) {
|
||||
header {
|
||||
background-color: #55A0E0;
|
||||
background-size: auto 200px;
|
||||
}
|
||||
|
||||
.header-text {
|
||||
font-size: 44px;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
height: 120px;
|
||||
width: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1000px) {
|
||||
header {
|
||||
background-color: #55A0E0;
|
||||
background-image: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 632px) {
|
||||
.header-text {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.row {
|
||||
padding: 10px 0px 0 10px;
|
||||
max-width: 490px !important;
|
||||
min-width: 410px !important;
|
||||
}
|
||||
|
||||
.endpoint {
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.main-text {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.step-container dl>dd {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.column {
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
height: 110px;
|
||||
width: 110px;
|
||||
}
|
||||
|
||||
.how-to-iframe {
|
||||
max-width: 480px !important;
|
||||
min-width: 400px !important;
|
||||
height: 650px !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.remove-frame-height {
|
||||
max-height: 10px;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
loadFrame();
|
||||
});
|
||||
var loadFrame = function () {
|
||||
var iframe = document.createElement('iframe');
|
||||
iframe.setAttribute("id", "iframe");
|
||||
var offLineHTMLContent = "";
|
||||
var frameElement = document.getElementById("how-to-iframe");
|
||||
if (window.navigator.onLine) {
|
||||
iframe.src = 'https://docs.botframework.com/static/abs/pages/f5.htm';
|
||||
iframe.setAttribute("scrolling", "no");
|
||||
iframe.setAttribute("frameborder", "0");
|
||||
iframe.setAttribute("width", "100%");
|
||||
iframe.setAttribute("height", "100%");
|
||||
var frameDiv = document.getElementById("how-to-iframe");
|
||||
frameDiv.appendChild(iframe);
|
||||
} else {
|
||||
frameElement.classList.add("remove-frame-height");
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header class="header">
|
||||
<div class="header-inner-container">
|
||||
<div class="header-icon" style="display: inline-block"></div>
|
||||
<div class="header-text" style="display: inline-block">TeamsSkillBot</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="row">
|
||||
<div class="column" class="main-content-area">
|
||||
<div class="content-title">Your bot is ready!</div>
|
||||
<div class="main-text main-text-p1">You can test your bot in the Bot Framework Emulator<br />
|
||||
by connecting to http://localhost:39773/api/messages.</div>
|
||||
<div class="main-text download-the-emulator"><a class="ctaLink"
|
||||
href="https://aka.ms/bot-framework-F5-download-emulator" target="_blank">Download the Emulator</a>
|
||||
</div>
|
||||
<div class="main-text">Visit <a class="ctaLink" href="https://aka.ms/bot-framework-F5-abs-home"
|
||||
target="_blank">Azure
|
||||
Bot Service</a> to register your bot and add it to<br />
|
||||
various channels. The bot's endpoint URL typically looks
|
||||
like this:</div>
|
||||
<div class="endpoint">https://<i>your_bots_hostname</i>/api/messages</div>
|
||||
</div>
|
||||
<div class="column how-to-iframe" id="how-to-iframe"></div>
|
||||
</div>
|
||||
<div class="ms-logo-container">
|
||||
<div class="ms-logo"></div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"$schema": "https://schemas.botframework.com/schemas/skills/skill-manifest-2.0.0.json",
|
||||
"$id": "TeamsSkillBot",
|
||||
"name": "Skill bot for Teams",
|
||||
"version": "1.0",
|
||||
"description": "This is a of a bot with skills for teams",
|
||||
"publisherName": "Microsoft",
|
||||
"privacyUrl": "https://teamsskillbot.contoso.com/privacy.html",
|
||||
"copyright": "Copyright (c) Microsoft Corporation. All rights reserved.",
|
||||
"license": "",
|
||||
"iconUrl": "https://teamsskillbot.contoso.com/icon.png",
|
||||
"tags": [
|
||||
"sample",
|
||||
"teams"
|
||||
],
|
||||
"endpoints": [
|
||||
{
|
||||
"name": "default",
|
||||
"protocol": "BotFrameworkV3",
|
||||
"description": "Default endpoint for the skill",
|
||||
"endpointUrl": "http://teamsskillbot.contoso.com/api/messages",
|
||||
"msAppId": "00000000-0000-0000-0000-000000000000"
|
||||
}
|
||||
],
|
||||
"activities": {
|
||||
"taskModule": {
|
||||
"description": "Task module sample.",
|
||||
"type": "invoke",
|
||||
"name": "TeamsTaskModule"
|
||||
},
|
||||
"cardAction": {
|
||||
"description": "Card action sample.",
|
||||
"type": "invoke",
|
||||
"name": "TeamsCardAction"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,21 +26,27 @@ namespace Microsoft.BotBuilderSamples.EchoSkillBot.Authentication
|
|||
throw new ArgumentNullException(nameof(config));
|
||||
}
|
||||
|
||||
// AllowedCallers is the setting in appsettings.json file
|
||||
// that consists of the list of parent bot ids that are allowed to access the skill
|
||||
// to add a new parent bot simply go to the AllowedCallers and add
|
||||
// the parent bot's microsoft app id to the list
|
||||
// AllowedCallers is the setting in the appsettings.json file
|
||||
// that consists of the list of parent bot IDs that are allowed to access the skill.
|
||||
// To add a new parent bot, simply edit the AllowedCallers and add
|
||||
// the parent bot's Microsoft app ID to the list.
|
||||
// In this sample, we allow all callers if AllowedCallers contains an "*".
|
||||
var section = config.GetSection(ConfigKey);
|
||||
var appsList = section.Get<string[]>();
|
||||
_allowedCallers = appsList != null ? new List<string>(appsList) : null;
|
||||
if (appsList == null)
|
||||
{
|
||||
throw new ArgumentNullException($"\"{ConfigKey}\" not found in configuration.");
|
||||
}
|
||||
|
||||
_allowedCallers = new List<string>(appsList);
|
||||
}
|
||||
|
||||
public override Task ValidateClaimsAsync(IList<Claim> claims)
|
||||
{
|
||||
// if _allowedCallers is null we allow all calls
|
||||
if (_allowedCallers != null && SkillValidation.IsSkillClaim(claims))
|
||||
// If _allowedCallers contains an "*", we allow all callers.
|
||||
if (SkillValidation.IsSkillClaim(claims) && !_allowedCallers.Contains("*"))
|
||||
{
|
||||
// Check that the appId claim in the skill request is in the list of skills configured for this bot.
|
||||
// Check that the appId claim in the skill request is in the list of callers configured for this bot.
|
||||
var appId = JwtTokenValidation.GetAppIdFromClaims(claims);
|
||||
if (!_allowedCallers.Contains(appId))
|
||||
{
|
||||
|
|
|
@ -15,15 +15,18 @@ namespace Microsoft.BotBuilderSamples.EchoSkillBot.Bots
|
|||
if (turnContext.Activity.Text.Contains("end") || turnContext.Activity.Text.Contains("stop"))
|
||||
{
|
||||
// Send End of conversation at the end.
|
||||
await turnContext.SendActivityAsync(MessageFactory.Text($"ending conversation from the skill..."), cancellationToken);
|
||||
var messageText = $"ending conversation from the skill...";
|
||||
await turnContext.SendActivityAsync(MessageFactory.Text(messageText, messageText, InputHints.IgnoringInput), cancellationToken);
|
||||
var endOfConversation = Activity.CreateEndOfConversationActivity();
|
||||
endOfConversation.Code = EndOfConversationCodes.CompletedSuccessfully;
|
||||
await turnContext.SendActivityAsync(endOfConversation, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
await turnContext.SendActivityAsync(MessageFactory.Text($"Echo (dotnet core 3.1) : {turnContext.Activity.Text}"), cancellationToken);
|
||||
await turnContext.SendActivityAsync(MessageFactory.Text("Say \"end\" or \"stop\" and I'll end the conversation and back to the parent."), cancellationToken);
|
||||
var messageText = $"Echo: {turnContext.Activity.Text}";
|
||||
await turnContext.SendActivityAsync(MessageFactory.Text(messageText, messageText, InputHints.IgnoringInput), cancellationToken);
|
||||
messageText = "Say \"end\" or \"stop\" and I'll end the conversation and back to the parent.";
|
||||
await turnContext.SendActivityAsync(MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput), cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
<LangVersion>latest</LangVersion>
|
||||
<AssemblyName>Microsoft.BotBuilderSamples.EchoSkillBot</AssemblyName>
|
||||
<RootNamespace>Microsoft.BotBuilderSamples.EchoSkillBot</RootNamespace>
|
||||
<UserSecretsId>d3e58f1c-0841-4154-8a6e-c4dfc5ae3edf</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -16,4 +15,10 @@
|
|||
<ProjectReference Include="..\..\..\..\libraries\integration\Microsoft.Bot.Builder.Integration.AspNet.Core\Microsoft.Bot.Builder.Integration.AspNet.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Update="appsettings.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.EchoSkillBot
|
||||
{
|
||||
|
@ -17,6 +18,11 @@ namespace Microsoft.BotBuilderSamples.EchoSkillBot
|
|||
Host.CreateDefaultBuilder(args)
|
||||
.ConfigureWebHostDefaults(webBuilder =>
|
||||
{
|
||||
webBuilder.ConfigureLogging((logging) =>
|
||||
{
|
||||
logging.AddDebug();
|
||||
logging.AddConsole();
|
||||
});
|
||||
webBuilder.UseStartup<Startup>();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.Bot.Builder.TraceExtensions;
|
||||
|
@ -14,15 +15,29 @@ namespace Microsoft.BotBuilderSamples.EchoSkillBot
|
|||
{
|
||||
public class SkillAdapterWithErrorHandler : BotFrameworkHttpAdapter
|
||||
{
|
||||
public SkillAdapterWithErrorHandler(IConfiguration configuration, ICredentialProvider credentialProvider, AuthenticationConfiguration authConfig, ILogger<BotFrameworkHttpAdapter> logger, ConversationState conversationState = null)
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public SkillAdapterWithErrorHandler(IConfiguration configuration, ICredentialProvider credentialProvider, AuthenticationConfiguration authConfig, ILogger<BotFrameworkHttpAdapter> logger)
|
||||
: base(configuration, credentialProvider, authConfig, logger: logger)
|
||||
{
|
||||
OnTurnError = async (turnContext, exception) =>
|
||||
{
|
||||
// Log any leaked exception from the application.
|
||||
logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}");
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
OnTurnError = HandleTurnError;
|
||||
}
|
||||
|
||||
// Send a message to the user
|
||||
private async Task HandleTurnError(ITurnContext turnContext, Exception exception)
|
||||
{
|
||||
// Log any leaked exception from the application.
|
||||
_logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}");
|
||||
|
||||
await SendErrorMessageAsync(turnContext, exception);
|
||||
await SendEoCToParentAsync(turnContext, exception);
|
||||
}
|
||||
|
||||
private async Task SendErrorMessageAsync(ITurnContext turnContext, Exception exception)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Send a message to the user.
|
||||
var errorMessageText = "The skill encountered an error or bug.";
|
||||
var errorMessage = MessageFactory.Text(errorMessageText, errorMessageText, InputHints.IgnoringInput);
|
||||
await turnContext.SendActivityAsync(errorMessage);
|
||||
|
@ -31,32 +46,32 @@ namespace Microsoft.BotBuilderSamples.EchoSkillBot
|
|||
errorMessage = MessageFactory.Text(errorMessageText, errorMessageText, InputHints.ExpectingInput);
|
||||
await turnContext.SendActivityAsync(errorMessage);
|
||||
|
||||
if (conversationState != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Delete the conversationState for the current conversation to prevent the
|
||||
// bot from getting stuck in a error-loop caused by being in a bad state.
|
||||
// ConversationState should be thought of as similar to "cookie-state" in a Web pages.
|
||||
await conversationState.DeleteAsync(turnContext);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, $"Exception caught on attempting to Delete ConversationState : {ex}");
|
||||
}
|
||||
}
|
||||
// Send a trace activity, which will be displayed in the Bot Framework Emulator.
|
||||
// Note: we return the entire exception in the value property to help the developer;
|
||||
// this should not be done in production.
|
||||
await turnContext.TraceActivityAsync("OnTurnError Trace", exception.ToString(), "https://www.botframework.com/schemas/error", "TurnError");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Exception caught in SendErrorMessageAsync : {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
// Send and EndOfConversation activity to the skill caller with the error to end the conversation
|
||||
private async Task SendEoCToParentAsync(ITurnContext turnContext, Exception exception)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Send an EndOfConversation activity to the skill caller with the error to end the conversation,
|
||||
// and let the caller decide what to do.
|
||||
var endOfConversation = Activity.CreateEndOfConversationActivity();
|
||||
endOfConversation.Code = "SkillError";
|
||||
endOfConversation.Text = exception.Message;
|
||||
await turnContext.SendActivityAsync(endOfConversation);
|
||||
|
||||
// Send a trace activity, which will be displayed in the Bot Framework Emulator
|
||||
// Note: we return the entire exception in the value property to help the developer, this should not be done in prod.
|
||||
await turnContext.TraceActivityAsync("OnTurnError Trace", exception.ToString(), "https://www.botframework.com/schemas/error", "TurnError");
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Exception caught in SendEoCToParentAsync : {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,18 +17,10 @@ namespace Microsoft.BotBuilderSamples.EchoSkillBot
|
|||
{
|
||||
public class Startup
|
||||
{
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
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.AddControllers()
|
||||
.AddNewtonsoftJson();
|
||||
services.AddControllers().AddNewtonsoftJson();
|
||||
|
||||
// Configure credentials
|
||||
services.AddSingleton<ICredentialProvider, ConfigurationCredentialProvider>();
|
||||
|
@ -51,21 +43,14 @@ namespace Microsoft.BotBuilderSamples.EchoSkillBot
|
|||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.UseDefaultFiles();
|
||||
app.UseStaticFiles();
|
||||
|
||||
//app.UseHttpsRedirection(); Enable this to support https
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllers();
|
||||
});
|
||||
app.UseDefaultFiles()
|
||||
.UseStaticFiles()
|
||||
.UseRouting()
|
||||
.UseAuthorization()
|
||||
.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllers();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,5 +9,11 @@
|
|||
"AllowedHosts": "*",
|
||||
"MicrosoftAppId": "",
|
||||
"MicrosoftAppPassword": "",
|
||||
"AllowedCallers": []
|
||||
|
||||
// This is a comma separate list with the App IDs that will have access to the skill.
|
||||
// This setting is used in AllowedCallersClaimsValidator.
|
||||
// Examples:
|
||||
// [ "*" ] allows all callers.
|
||||
// [ "AppId1", "AppId2" ] only allows access to parent bots with "AppId1" and "AppId2".
|
||||
"AllowedCallers": [ "*" ]
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
"protocol": "BotFrameworkV3",
|
||||
"description": "Default endpoint for the skill",
|
||||
"endpointUrl": "https://ggechoskillbot.azurewebsites.net/api/messages",
|
||||
"msAppId": "0be01cfa-478e-4ec2-b2cc-9a4ec02f101b"
|
||||
"msAppId": "f3fe8762-e50c-4688-b202-a040f522d916"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -25,11 +25,11 @@ namespace Microsoft.BotBuilderSamples.SimpleRootBot
|
|||
private readonly SkillHttpClient _skillClient;
|
||||
private readonly SkillsConfiguration _skillsConfig;
|
||||
|
||||
public AdapterWithErrorHandler(IConfiguration configuration, ILogger<BotFrameworkHttpAdapter> logger, ConversationState conversationState = null, SkillHttpClient skillClient = null, SkillsConfiguration skillsConfig = null)
|
||||
public AdapterWithErrorHandler(IConfiguration configuration, ILogger<BotFrameworkHttpAdapter> logger, ConversationState conversationState, SkillHttpClient skillClient = null, SkillsConfiguration skillsConfig = null)
|
||||
: base(configuration, logger)
|
||||
{
|
||||
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
|
||||
_conversationState = conversationState;
|
||||
_conversationState = conversationState ?? throw new ArgumentNullException(nameof(conversationState));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_skillClient = skillClient;
|
||||
_skillsConfig = skillsConfig;
|
||||
|
@ -71,7 +71,7 @@ namespace Microsoft.BotBuilderSamples.SimpleRootBot
|
|||
|
||||
private async Task EndSkillConversationAsync(ITurnContext turnContext)
|
||||
{
|
||||
if (_conversationState == null || _skillClient == null || _skillsConfig == null)
|
||||
if (_skillClient == null || _skillsConfig == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -103,19 +103,16 @@ namespace Microsoft.BotBuilderSamples.SimpleRootBot
|
|||
|
||||
private async Task ClearConversationStateAsync(ITurnContext turnContext)
|
||||
{
|
||||
if (_conversationState != null)
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
// Delete the conversationState for the current conversation to prevent the
|
||||
// bot from getting stuck in a error-loop caused by being in a bad state.
|
||||
// ConversationState should be thought of as similar to "cookie-state" in a Web pages.
|
||||
await _conversationState.DeleteAsync(turnContext);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Exception caught on attempting to Delete ConversationState : {ex}");
|
||||
}
|
||||
// Delete the conversationState for the current conversation to prevent the
|
||||
// bot from getting stuck in a error-loop caused by being in a bad state.
|
||||
// ConversationState should be thought of as similar to "cookie-state" in a Web pages.
|
||||
await _conversationState.DeleteAsync(turnContext);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Exception caught on attempting to Delete ConversationState : {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -116,6 +116,9 @@ namespace Microsoft.BotBuilderSamples.SimpleRootBot.Bots
|
|||
|
||||
// We are back at the root
|
||||
await turnContext.SendActivityAsync(MessageFactory.Text("Back in the root bot. Say \"skill\" and I'll patch you through"), cancellationToken);
|
||||
|
||||
// Save conversation state
|
||||
await _conversationState.SaveChangesAsync(turnContext, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
|
||||
|
@ -124,7 +127,7 @@ namespace Microsoft.BotBuilderSamples.SimpleRootBot.Bots
|
|||
{
|
||||
if (member.Id != turnContext.Activity.Recipient.Id)
|
||||
{
|
||||
await turnContext.SendActivityAsync(MessageFactory.Text("Hello and welcome! (dotnet core 3.1)"), cancellationToken);
|
||||
await turnContext.SendActivityAsync(MessageFactory.Text("Hello and welcome!"), cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.BotBuilderSamples.SimpleRootBot
|
||||
{
|
||||
|
@ -17,6 +18,11 @@ namespace Microsoft.BotBuilderSamples.SimpleRootBot
|
|||
Host.CreateDefaultBuilder(args)
|
||||
.ConfigureWebHostDefaults(webBuilder =>
|
||||
{
|
||||
webBuilder.ConfigureLogging((logging) =>
|
||||
{
|
||||
logging.AddDebug();
|
||||
logging.AddConsole();
|
||||
});
|
||||
webBuilder.UseStartup<Startup>();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -12,15 +12,14 @@
|
|||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "default.htm",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"SimpleRootBot31": {
|
||||
"SimpleRootBot": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:5001;http://localhost:5000",
|
||||
"applicationUrl": "http://localhost:3978",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
<LangVersion>latest</LangVersion>
|
||||
<AssemblyName>Microsoft.BotBuilderSamples.SimpleRootBot</AssemblyName>
|
||||
<RootNamespace>Microsoft.BotBuilderSamples.SimpleRootBot</RootNamespace>
|
||||
<UserSecretsId>a52d1fa1-0d90-42d7-bbfc-2d776e8b7804</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -16,4 +15,9 @@
|
|||
<ProjectReference Include="..\..\..\..\libraries\integration\Microsoft.Bot.Builder.Integration.AspNet.Core\Microsoft.Bot.Builder.Integration.AspNet.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Update="appsettings.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -11,7 +11,6 @@ using Microsoft.Bot.Builder.Skills;
|
|||
using Microsoft.Bot.Connector.Authentication;
|
||||
using Microsoft.BotBuilderSamples.SimpleRootBot.Authentication;
|
||||
using Microsoft.BotBuilderSamples.SimpleRootBot.Bots;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
|
@ -19,18 +18,10 @@ namespace Microsoft.BotBuilderSamples.SimpleRootBot
|
|||
{
|
||||
public class Startup
|
||||
{
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
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.AddControllers()
|
||||
.AddNewtonsoftJson();
|
||||
services.AddControllers().AddNewtonsoftJson();
|
||||
|
||||
// Configure credentials
|
||||
services.AddSingleton<ICredentialProvider, ConfigurationCredentialProvider>();
|
||||
|
@ -72,19 +63,14 @@ namespace Microsoft.BotBuilderSamples.SimpleRootBot
|
|||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.UseDefaultFiles();
|
||||
app.UseStaticFiles();
|
||||
|
||||
//app.UseHttpsRedirection(); Enable this to support https
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllers();
|
||||
});
|
||||
app.UseDefaultFiles()
|
||||
.UseStaticFiles()
|
||||
.UseRouting()
|
||||
.UseAuthorization()
|
||||
.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllers();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,9 @@ MinimumVisualStudioVersion = 10.0.40219.1
|
|||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Libraries", "Libraries", "{4269F3C3-6B42-419B-B64A-3E6DC0F1574A}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{AD743B78-D61F-4FBF-B620-FA83CE599A50}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
tests\Directory.Build.props = tests\Directory.Build.props
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.Dialogs.Adaptive", "libraries\Microsoft.Bot.Builder.Dialogs.Adaptive\Microsoft.Bot.Builder.Dialogs.Adaptive.csproj", "{3CF175CF-1AF4-4109-96CB-221684DCED7D}"
|
||||
EndProject
|
||||
|
@ -69,6 +72,9 @@ EndProject
|
|||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.TestBot", "tests\Microsoft.Bot.Builder.TestBot\Microsoft.Bot.Builder.TestBot.csproj", "{C113E0AE-5564-4389-BA39-183A8D574210}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "FunctionalTests", "FunctionalTests", "{8667F820-8ADA-4498-91AE-AE95DEE5227E}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
FunctionalTests\Directory.Build.props = FunctionalTests\Directory.Build.props
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.Testing", "libraries\Microsoft.Bot.Builder.Testing\Microsoft.Bot.Builder.Testing.csproj", "{060F070A-BBFA-490E-BE89-3844C857B771}"
|
||||
EndProject
|
||||
|
@ -94,6 +100,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.Langu
|
|||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6B6AFE9D-6FA5-4699-B0EB-62335FD431C8}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.editorconfig = .editorconfig
|
||||
bot.png = bot.png
|
||||
bot_icon.png = bot_icon.png
|
||||
BotBuilder-DotNet.ruleset = BotBuilder-DotNet.ruleset
|
||||
|
@ -102,16 +109,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||
tests\tests.schema = tests\tests.schema
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Schemas", "Schemas", "{EE56F2B6-4995-4E8F-ACFF-310AF0A4DA0F}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
schemas\baseComponent.schema = schemas\baseComponent.schema
|
||||
schemas\component.schema = schemas\component.schema
|
||||
schemas\readme.md = schemas\readme.md
|
||||
schemas\sdk.schema = schemas\sdk.schema
|
||||
schemas\update.cmd = schemas\update.cmd
|
||||
schemas\updateBranch.cmd = schemas\updateBranch.cmd
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.TestBot.Json", "tests\Microsoft.Bot.Builder.TestBot.Json\Microsoft.Bot.Builder.TestBot.Json.csproj", "{2454BBCD-77BC-4E3D-B5A6-3562BED898D6}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.AI.Luis.TestUtils", "tests\Microsoft.Bot.Builder.AI.Luis.TestUtils\Microsoft.Bot.Builder.AI.Luis.TestUtils.csproj", "{685271A8-6C69-46E4-9B11-89AF9761CE0A}"
|
||||
|
@ -152,9 +149,6 @@ EndProject
|
|||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EchoSkillBot", "FunctionalTests\Skills\SimpleBotToBot\EchoSkillBot\EchoSkillBot.csproj", "{C8F3C6E5-6A21-4F77-ADF7-30119D836A4D}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DialogToDialog", "DialogToDialog", "{3E023AB7-FE1F-41B1-9EF4-1550BCE1DC37}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
FunctionalTests\Skills\DialogToDialog\README.md = FunctionalTests\Skills\DialogToDialog\README.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DialogRootBot", "FunctionalTests\Skills\DialogToDialog\DialogRootBot\DialogRootBot.csproj", "{E65DC262-CA77-41F6-8439-02C1917874DD}"
|
||||
EndProject
|
||||
|
@ -170,6 +164,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdaptiveRootBot", "Function
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdaptiveSkillBot", "FunctionalTests\Skills\Adaptive\AdaptiveSkillBot\AdaptiveSkillBot.csproj", "{3EA28B80-440E-4919-9850-31236968BC04}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TeamsSkillBot", "FunctionalTests\Skills\DialogToDialog\TeamsSkillBot\TeamsSkillBot.csproj", "{9CE278DD-0560-4D4B-B6BB-D118FBA20D4E}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -658,6 +654,14 @@ Global
|
|||
{3EA28B80-440E-4919-9850-31236968BC04}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3EA28B80-440E-4919-9850-31236968BC04}.Release-Windows|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3EA28B80-440E-4919-9850-31236968BC04}.Release-Windows|Any CPU.Build.0 = Release|Any CPU
|
||||
{9CE278DD-0560-4D4B-B6BB-D118FBA20D4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9CE278DD-0560-4D4B-B6BB-D118FBA20D4E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9CE278DD-0560-4D4B-B6BB-D118FBA20D4E}.Debug-Windows|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9CE278DD-0560-4D4B-B6BB-D118FBA20D4E}.Debug-Windows|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9CE278DD-0560-4D4B-B6BB-D118FBA20D4E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9CE278DD-0560-4D4B-B6BB-D118FBA20D4E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{9CE278DD-0560-4D4B-B6BB-D118FBA20D4E}.Release-Windows|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9CE278DD-0560-4D4B-B6BB-D118FBA20D4E}.Release-Windows|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -731,6 +735,7 @@ Global
|
|||
{AE7143FE-C460-449D-AA50-EFA4820EBADE} = {3C16F609-61D8-49C5-A243-9D32B9491D91}
|
||||
{1E638E89-43B9-4381-8CC7-B65548DB0070} = {AE7143FE-C460-449D-AA50-EFA4820EBADE}
|
||||
{3EA28B80-440E-4919-9850-31236968BC04} = {AE7143FE-C460-449D-AA50-EFA4820EBADE}
|
||||
{9CE278DD-0560-4D4B-B6BB-D118FBA20D4E} = {3E023AB7-FE1F-41B1-9EF4-1550BCE1DC37}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {7173C9F3-A7F9-496E-9078-9156E35D6E16}
|
||||
|
|
Загрузка…
Ссылка в новой задаче