From 7a2761ef85e3ebedfd8e21997ce3ccd3b8f875f1 Mon Sep 17 00:00:00 2001 From: singhk97 <115390646+singhk97@users.noreply.github.com> Date: Thu, 30 Nov 2023 15:52:58 -0800 Subject: [PATCH] [C#] chore: Remove questbot (#962) ## Linked issues closes: #959 (issue number) ## Details - Remove quest bot since it's not ready for the next release. In the future we can decide to add it back by finding it in the commit history. ## Attestation Checklist - [x] My code follows the style guidelines of this project - I have checked for/fixed spelling, linting, and other errors - I have commented my code for clarity - I have made corresponding changes to the documentation (we use [TypeDoc](https://typedoc.org/) to document our code) - My changes generate no new warnings - I have added tests that validates my changes, and provides sufficient test coverage. I have tested with: - Local testing - E2E testing in Teams - New and existing unit tests pass locally with my changes --- dotnet/samples/04.q.questBot/.gitignore | 25 -- .../04.q.questBot/Actions/InventoryAction.cs | 143 -------- .../04.q.questBot/Actions/LocationAction.cs | 77 ---- .../04.q.questBot/Actions/MapAction.cs | 37 -- .../04.q.questBot/Actions/PlayerAction.cs | 120 ------ .../04.q.questBot/Actions/QuestAction.cs | 142 -------- .../04.q.questBot/Actions/QuestBotActions.cs | 62 ---- .../04.q.questBot/Actions/StoryAction.cs | 33 -- .../04.q.questBot/Actions/TimeAction.cs | 152 -------- .../04.q.questBot/AdapterWithErrorHandler.cs | 29 -- dotnet/samples/04.q.questBot/Conditions.cs | 341 ------------------ dotnet/samples/04.q.questBot/Config.cs | 27 -- .../Controllers/BotController.cs | 32 -- .../samples/04.q.questBot/Models/Campaign.cs | 19 - .../04.q.questBot/Models/CampaignObjective.cs | 16 - .../04.q.questBot/Models/DataEntities.cs | 34 -- .../samples/04.q.questBot/Models/ItemList.cs | 142 -------- .../samples/04.q.questBot/Models/Location.cs | 16 - dotnet/samples/04.q.questBot/Models/Map.cs | 257 ------------- .../04.q.questBot/Models/MapLocation.cs | 31 -- dotnet/samples/04.q.questBot/Models/Quest.cs | 15 - dotnet/samples/04.q.questBot/Program.cs | 153 -------- .../Prompts/CreateCampaign/config.json | 15 - .../Prompts/CreateCampaign/skprompt.txt | 32 -- .../Prompts/FindDifferences/config.json | 15 - .../Prompts/FindDifferences/skprompt.txt | 7 - .../04.q.questBot/Prompts/Help/config.json | 15 - .../04.q.questBot/Prompts/Help/skprompt.txt | 34 -- .../04.q.questBot/Prompts/Intro/config.json | 15 - .../04.q.questBot/Prompts/Intro/skprompt.txt | 36 -- .../Prompts/ListItems/config.json | 15 - .../Prompts/ListItems/skprompt.txt | 17 - .../Prompts/NewObjective/config.json | 15 - .../Prompts/NewObjective/skprompt.txt | 53 --- .../04.q.questBot/Prompts/Prompt/config.json | 15 - .../04.q.questBot/Prompts/Prompt/skprompt.txt | 134 ------- .../Prompts/QuestDetails/config.json | 15 - .../Prompts/QuestDetails/skprompt.txt | 9 - .../Prompts/UpdatePlayer/config.json | 15 - .../Prompts/UpdatePlayer/skprompt.txt | 15 - .../04.q.questBot/Prompts/UseMap/config.json | 15 - .../04.q.questBot/Prompts/UseMap/skprompt.txt | 55 --- .../Properties/launchSettings.json | 27 -- dotnet/samples/04.q.questBot/QuestBot.csproj | 47 --- dotnet/samples/04.q.questBot/QuestBot.sln | 27 -- dotnet/samples/04.q.questBot/README.md | 113 ------ .../04.q.questBot/ResponseGenerator.cs | 163 --------- .../samples/04.q.questBot/ResponseParser.cs | 81 ----- .../State/QuestConversationState.cs | 119 ------ .../samples/04.q.questBot/State/QuestState.cs | 11 - .../04.q.questBot/State/QuestStateManager.cs | 11 - .../04.q.questBot/State/QuestTempState.cs | 78 ---- .../04.q.questBot/State/QuestUserState.cs | 39 -- .../Store/LastWriterWinsMemoryStore.cs | 37 -- .../Store/LastWriterWinsStore.cs | 38 -- dotnet/samples/04.q.questBot/TeamsQuestBot.cs | 69 ---- .../04.q.questBot/TeamsQuestBotHandlers.cs | 323 ----------------- .../04.q.questBot/appPackage/color.png | 3 - .../04.q.questBot/appPackage/manifest.json | 50 --- .../04.q.questBot/appPackage/outline.png | 3 - .../appsettings.Development.json | 19 - dotnet/samples/04.q.questBot/appsettings.json | 19 - dotnet/samples/04.q.questBot/assets/help.png | 3 - dotnet/samples/04.q.questBot/assets/move.png | 3 - dotnet/samples/04.q.questBot/assets/start.png | 3 - dotnet/samples/04.q.questBot/env/.env.dev | 16 - .../samples/04.q.questBot/env/.env.dev.user | 12 - dotnet/samples/04.q.questBot/env/.env.local | 12 - .../samples/04.q.questBot/infra/azure.bicep | 99 ----- .../04.q.questBot/infra/azure.parameters.json | 30 -- .../infra/botRegistration/azurebot.bicep | 37 -- .../infra/botRegistration/readme.md | 1 - .../samples/04.q.questBot/teamsapp.local.yml | 86 ----- dotnet/samples/04.q.questBot/teamsapp.yml | 96 ----- 74 files changed, 4120 deletions(-) delete mode 100644 dotnet/samples/04.q.questBot/.gitignore delete mode 100644 dotnet/samples/04.q.questBot/Actions/InventoryAction.cs delete mode 100644 dotnet/samples/04.q.questBot/Actions/LocationAction.cs delete mode 100644 dotnet/samples/04.q.questBot/Actions/MapAction.cs delete mode 100644 dotnet/samples/04.q.questBot/Actions/PlayerAction.cs delete mode 100644 dotnet/samples/04.q.questBot/Actions/QuestAction.cs delete mode 100644 dotnet/samples/04.q.questBot/Actions/QuestBotActions.cs delete mode 100644 dotnet/samples/04.q.questBot/Actions/StoryAction.cs delete mode 100644 dotnet/samples/04.q.questBot/Actions/TimeAction.cs delete mode 100644 dotnet/samples/04.q.questBot/AdapterWithErrorHandler.cs delete mode 100644 dotnet/samples/04.q.questBot/Conditions.cs delete mode 100644 dotnet/samples/04.q.questBot/Config.cs delete mode 100644 dotnet/samples/04.q.questBot/Controllers/BotController.cs delete mode 100644 dotnet/samples/04.q.questBot/Models/Campaign.cs delete mode 100644 dotnet/samples/04.q.questBot/Models/CampaignObjective.cs delete mode 100644 dotnet/samples/04.q.questBot/Models/DataEntities.cs delete mode 100644 dotnet/samples/04.q.questBot/Models/ItemList.cs delete mode 100644 dotnet/samples/04.q.questBot/Models/Location.cs delete mode 100644 dotnet/samples/04.q.questBot/Models/Map.cs delete mode 100644 dotnet/samples/04.q.questBot/Models/MapLocation.cs delete mode 100644 dotnet/samples/04.q.questBot/Models/Quest.cs delete mode 100644 dotnet/samples/04.q.questBot/Program.cs delete mode 100644 dotnet/samples/04.q.questBot/Prompts/CreateCampaign/config.json delete mode 100644 dotnet/samples/04.q.questBot/Prompts/CreateCampaign/skprompt.txt delete mode 100644 dotnet/samples/04.q.questBot/Prompts/FindDifferences/config.json delete mode 100644 dotnet/samples/04.q.questBot/Prompts/FindDifferences/skprompt.txt delete mode 100644 dotnet/samples/04.q.questBot/Prompts/Help/config.json delete mode 100644 dotnet/samples/04.q.questBot/Prompts/Help/skprompt.txt delete mode 100644 dotnet/samples/04.q.questBot/Prompts/Intro/config.json delete mode 100644 dotnet/samples/04.q.questBot/Prompts/Intro/skprompt.txt delete mode 100644 dotnet/samples/04.q.questBot/Prompts/ListItems/config.json delete mode 100644 dotnet/samples/04.q.questBot/Prompts/ListItems/skprompt.txt delete mode 100644 dotnet/samples/04.q.questBot/Prompts/NewObjective/config.json delete mode 100644 dotnet/samples/04.q.questBot/Prompts/NewObjective/skprompt.txt delete mode 100644 dotnet/samples/04.q.questBot/Prompts/Prompt/config.json delete mode 100644 dotnet/samples/04.q.questBot/Prompts/Prompt/skprompt.txt delete mode 100644 dotnet/samples/04.q.questBot/Prompts/QuestDetails/config.json delete mode 100644 dotnet/samples/04.q.questBot/Prompts/QuestDetails/skprompt.txt delete mode 100644 dotnet/samples/04.q.questBot/Prompts/UpdatePlayer/config.json delete mode 100644 dotnet/samples/04.q.questBot/Prompts/UpdatePlayer/skprompt.txt delete mode 100644 dotnet/samples/04.q.questBot/Prompts/UseMap/config.json delete mode 100644 dotnet/samples/04.q.questBot/Prompts/UseMap/skprompt.txt delete mode 100644 dotnet/samples/04.q.questBot/Properties/launchSettings.json delete mode 100644 dotnet/samples/04.q.questBot/QuestBot.csproj delete mode 100644 dotnet/samples/04.q.questBot/QuestBot.sln delete mode 100644 dotnet/samples/04.q.questBot/README.md delete mode 100644 dotnet/samples/04.q.questBot/ResponseGenerator.cs delete mode 100644 dotnet/samples/04.q.questBot/ResponseParser.cs delete mode 100644 dotnet/samples/04.q.questBot/State/QuestConversationState.cs delete mode 100644 dotnet/samples/04.q.questBot/State/QuestState.cs delete mode 100644 dotnet/samples/04.q.questBot/State/QuestStateManager.cs delete mode 100644 dotnet/samples/04.q.questBot/State/QuestTempState.cs delete mode 100644 dotnet/samples/04.q.questBot/State/QuestUserState.cs delete mode 100644 dotnet/samples/04.q.questBot/Store/LastWriterWinsMemoryStore.cs delete mode 100644 dotnet/samples/04.q.questBot/Store/LastWriterWinsStore.cs delete mode 100644 dotnet/samples/04.q.questBot/TeamsQuestBot.cs delete mode 100644 dotnet/samples/04.q.questBot/TeamsQuestBotHandlers.cs delete mode 100644 dotnet/samples/04.q.questBot/appPackage/color.png delete mode 100644 dotnet/samples/04.q.questBot/appPackage/manifest.json delete mode 100644 dotnet/samples/04.q.questBot/appPackage/outline.png delete mode 100644 dotnet/samples/04.q.questBot/appsettings.Development.json delete mode 100644 dotnet/samples/04.q.questBot/appsettings.json delete mode 100644 dotnet/samples/04.q.questBot/assets/help.png delete mode 100644 dotnet/samples/04.q.questBot/assets/move.png delete mode 100644 dotnet/samples/04.q.questBot/assets/start.png delete mode 100644 dotnet/samples/04.q.questBot/env/.env.dev delete mode 100644 dotnet/samples/04.q.questBot/env/.env.dev.user delete mode 100644 dotnet/samples/04.q.questBot/env/.env.local delete mode 100644 dotnet/samples/04.q.questBot/infra/azure.bicep delete mode 100644 dotnet/samples/04.q.questBot/infra/azure.parameters.json delete mode 100644 dotnet/samples/04.q.questBot/infra/botRegistration/azurebot.bicep delete mode 100644 dotnet/samples/04.q.questBot/infra/botRegistration/readme.md delete mode 100644 dotnet/samples/04.q.questBot/teamsapp.local.yml delete mode 100644 dotnet/samples/04.q.questBot/teamsapp.yml diff --git a/dotnet/samples/04.q.questBot/.gitignore b/dotnet/samples/04.q.questBot/.gitignore deleted file mode 100644 index ee83f4452..000000000 --- a/dotnet/samples/04.q.questBot/.gitignore +++ /dev/null @@ -1,25 +0,0 @@ -# VS -.vs - -# User-specific files -*.user - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ - -# Teams Toolkit -env/.env.*.user -appPackage/build -.deployment -# Teams Toolkit Local Development -appsettings.Development.json -env/.env.local diff --git a/dotnet/samples/04.q.questBot/Actions/InventoryAction.cs b/dotnet/samples/04.q.questBot/Actions/InventoryAction.cs deleted file mode 100644 index 263400eec..000000000 --- a/dotnet/samples/04.q.questBot/Actions/InventoryAction.cs +++ /dev/null @@ -1,143 +0,0 @@ -using AdaptiveCards; -using Microsoft.Bot.Builder; -using Microsoft.Bot.Schema; -using Microsoft.Teams.AI.AI; -using Microsoft.Teams.AI.AI.Action; -using QuestBot.Models; -using QuestBot.State; -using System.Text.Json; - -namespace QuestBot.Actions -{ - public partial class QuestBotActions - { - [Action("inventory")] - public async Task InventoryAsync([ActionTurnContext] ITurnContext turnContext, [ActionTurnState] QuestState turnState, [ActionEntities] Dictionary entities) - { - ArgumentNullException.ThrowIfNull(turnContext); - ArgumentNullException.ThrowIfNull(turnState); - var data = GetData(entities); - var action = (data.Operation ?? string.Empty).Trim().ToLowerInvariant(); - switch (action) - { - case "update": - await UpdateListAsync(turnContext, turnState, data); - return true; - case "list": - return await PrintListAsync(turnContext, turnState); - default: - await turnContext.SendActivityAsync($"[inventory.{action}]"); - return true; - } - } - - /// - /// Prints the user's inventory to the conversation. - /// - private async Task PrintListAsync(ITurnContext context, QuestState state) - { - var items = state.User!.Inventory; - if (items != null && items.Count > 0) - { - state.Temp!.ListItems = items; - state.Temp!.ListType = "inventory"; - _application.AI.Prompts.Variables["listType"] = state.Temp!.ListType; - _application.AI.Prompts.Variables["listItems"] = JsonSerializer.Serialize(state.Temp!.ListItems); - _application.AI.Prompts.Variables["name"] = state.User!.Name ?? string.Empty; - var newResponse = await _application.AI.CompletePromptAsync(context, state, "ListItems", null, default); - if (!string.IsNullOrEmpty(newResponse)) - { - var card = ResponseParser.ParseAdaptiveCard(newResponse); - if (card != null) - { - await context.SendActivityAsync(MessageFactory.Attachment(new Attachment - { - ContentType = AdaptiveCard.ContentType, - Content = card.Card - })); - state.Temp.PlayerAnswered = true; - return true; - } - } - - await UpdateDMResponseAsync(context, state, ResponseGenerator.DataError()); - } - else - { - await UpdateDMResponseAsync(context, state, ResponseGenerator.EmptyInventory()); - } - - return false; - } - - /// - /// Updates the user's inventory based on the given data. - /// - private static async Task UpdateListAsync(ITurnContext context, QuestState state, DataEntities data) - { - var newItems = - state.User!.Inventory == null ? - new ItemList() : - new ItemList(state.User!.Inventory); - - // Remove items first - var changes = new List(); - var changeItems = ItemList.FromText(data.Items); - foreach (var removeItem in changeItems) - { - // Normalize the items name and count - // - This converts 'coins:1' to 'gold:10' - var normalizedItem = ItemList.MapTo(removeItem.Key, removeItem.Value); - if (string.IsNullOrEmpty(normalizedItem.Key)) - { - continue; - } - - if (normalizedItem.Value > 0) - { - // Add the item - if (newItems.ContainsKey(normalizedItem.Key)) - { - newItems[normalizedItem.Key] += normalizedItem.Value; - } - else - { - newItems[normalizedItem.Key] = normalizedItem.Value; - } - changes.Add($"+{normalizedItem.Value} ({normalizedItem.Key})"); - } - else if (normalizedItem.Value < 0) - { - // remove the item - var keyToRemove = newItems.SearchItem(normalizedItem.Key); - if (!string.IsNullOrEmpty(keyToRemove)) - { - if (normalizedItem.Value + newItems[keyToRemove] > 0) - { - changes.Add($"{normalizedItem.Value} ({keyToRemove})"); - newItems[keyToRemove] += normalizedItem.Value; - } - else - { - // Hallucinating number of items in inventory - changes.Add($"{newItems[keyToRemove]} ({keyToRemove})"); - newItems.Remove(keyToRemove); - } - } - else - { - // Hallucinating item as being in inventory - changes.Add($"{normalizedItem.Value} ({normalizedItem.Key})"); - } - } - } - - // Report inventory changes to user - var playerName = state.User.Name?.ToLowerInvariant() ?? string.Empty; - await context.SendActivityAsync($"{playerName}: {string.Join(", ", changes)}"); - - // Save inventory changes - state.User.Inventory = newItems; - } - } -} diff --git a/dotnet/samples/04.q.questBot/Actions/LocationAction.cs b/dotnet/samples/04.q.questBot/Actions/LocationAction.cs deleted file mode 100644 index 3e5ee9997..000000000 --- a/dotnet/samples/04.q.questBot/Actions/LocationAction.cs +++ /dev/null @@ -1,77 +0,0 @@ -using Microsoft.Bot.Builder; -using Microsoft.Teams.AI.AI.Action; -using QuestBot.Models; -using QuestBot.State; - -namespace QuestBot.Actions -{ - public partial class QuestBotActions - { - [Action("location")] - public async Task LocationAsync([ActionTurnContext] ITurnContext turnContext, [ActionTurnState] QuestState turnState, [ActionEntities] Dictionary entities) - { - ArgumentNullException.ThrowIfNull(turnContext); - ArgumentNullException.ThrowIfNull(turnState); - var data = GetData(entities); - var action = (data.Operation ?? string.Empty).Trim().ToLowerInvariant(); - switch (action) - { - case "change": - case "update": - await UpdateLocationAsync(turnContext, turnState, data); - return true; - default: - await turnContext.SendActivityAsync($"[location.{action}]"); - return true; - } - } - - /// - /// Calculates the encounter chance for a given location title. - /// - private static double GetEncounterChance(string title) - { - title = title.ToLowerInvariant(); - var location = Map.FindMapLocation(title); - if (location != null) - { - return location.EncounterChance; - } - else if (title.IndexOf("dungeon") >= 0 || title.IndexOf("cave") >= 0) - { - return 0.4; - } - else - { - return 0.2; - } - } - - /// - /// Updates the location of the conversation and sends a message to the user if the location has changed. - /// - private static async Task UpdateLocationAsync(ITurnContext context, QuestState state, DataEntities data) - { - var currentLocation = state.Conversation!.Location; - - // Create new location object - var title = (data.Title ?? string.Empty).Trim(); - var newLocation = new Location - { - Title = title, - Description = (data.Description ?? string.Empty).Trim(), - EncounterChance = GetEncounterChance(title) - }; - - // Has the location changed? - // - Ignore the change if the location hasn't changed. - if (!string.Equals(newLocation.Title, currentLocation?.Title, StringComparison.OrdinalIgnoreCase)) - { - state.Conversation.LocationTurn += 1; - await context.SendActivityAsync($"🧭 {newLocation.Title}
{string.Join("
", newLocation.Description.Split('\n'))}"); - } - - state.Temp!.PlayerAnswered = true; - } - } -} diff --git a/dotnet/samples/04.q.questBot/Actions/MapAction.cs b/dotnet/samples/04.q.questBot/Actions/MapAction.cs deleted file mode 100644 index fda8f4881..000000000 --- a/dotnet/samples/04.q.questBot/Actions/MapAction.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Microsoft.Bot.Builder; -using Microsoft.Teams.AI.AI.Action; -using QuestBot.State; - -namespace QuestBot.Actions -{ - public partial class QuestBotActions - { - [Action("map")] - public async Task MapAsync([ActionTurnContext] ITurnContext turnContext, [ActionTurnState] QuestState turnState, [ActionEntities] Dictionary entities) - { - ArgumentNullException.ThrowIfNull(turnContext); - ArgumentNullException.ThrowIfNull(turnState); - var data = GetData(entities); - var action = (data.Operation ?? string.Empty).Trim().ToLowerInvariant(); - switch (action) - { - case "query": - // Use the map to answer player - var newResponse = await _application.AI.CompletePromptAsync(turnContext, turnState, "UseMap", null, default); - if (!string.IsNullOrEmpty(newResponse)) - { - await UpdateDMResponseAsync(turnContext, turnState, string.Join("
", TrimPromptResponse(newResponse).Split('\n'))); - turnState.Temp!.PlayerAnswered = true; - } - else - { - await UpdateDMResponseAsync(turnContext, turnState, ResponseGenerator.DataError()); - } - return false; - default: - await turnContext.SendActivityAsync($"[map.{action}]"); - return true; - } - } - } -} diff --git a/dotnet/samples/04.q.questBot/Actions/PlayerAction.cs b/dotnet/samples/04.q.questBot/Actions/PlayerAction.cs deleted file mode 100644 index 0d33f0425..000000000 --- a/dotnet/samples/04.q.questBot/Actions/PlayerAction.cs +++ /dev/null @@ -1,120 +0,0 @@ -using Microsoft.Bot.Builder; -using Microsoft.Teams.AI.AI; -using Microsoft.Teams.AI.AI.Action; -using QuestBot.Models; -using QuestBot.State; -using System.Text.Json; - -namespace QuestBot.Actions -{ - public partial class QuestBotActions - { - [Action("player")] - public async Task PlayerAsync([ActionTurnContext] ITurnContext turnContext, [ActionTurnState] QuestState turnState, [ActionEntities] Dictionary entities) - { - ArgumentNullException.ThrowIfNull(turnContext); - ArgumentNullException.ThrowIfNull(turnState); - var data = GetData(entities); - var action = (data.Operation ?? string.Empty).Trim().ToLowerInvariant(); - switch (action) - { - case "update": - return await UpdatePlayerAsync(turnContext, turnState, data); - default: - await turnContext.SendActivityAsync($"[player.{action}]"); - return true; - } - } - - /// - /// Updates the player's information. - /// - private async Task UpdatePlayerAsync(ITurnContext context, QuestState state, DataEntities data) - { - // Check for name change - var newName = (data.Name ?? string.Empty).Trim(); - if (!string.IsNullOrEmpty(newName)) - { - // Update players for current session - var newPlayers = - state.Conversation!.Players == null ? - new List() : - new List(state.Conversation.Players); - newPlayers.Remove(state.User!.Name ?? string.Empty); - newPlayers.Add(newName); - - // Update name and notify user - state.User.Name = newName; - } - - // Check for change or default values - // - Lets update everything on first name change - var backstoryChange = (data.Backstory ?? string.Empty).Trim(); - if (string.IsNullOrEmpty(backstoryChange) && string.Equals(state.User!.Backstory, QuestUserState.DEFAULT_BACKSTORY)) - { - backstoryChange = QuestUserState.DEFAULT_BACKSTORY; - } - - var equippedChange = (data.Equipped ?? string.Empty).Trim(); - if (string.IsNullOrEmpty(equippedChange) && string.Equals(state.User!.Equipped, QuestUserState.DEFAULT_EQUIPPED)) - { - equippedChange = QuestUserState.DEFAULT_EQUIPPED; - } - - // Update backstory and equipped - if (!string.IsNullOrEmpty(backstoryChange) || !string.IsNullOrEmpty(equippedChange)) - { - state.Temp!.BackstoryChange = string.IsNullOrEmpty(backstoryChange) ? "no change" : backstoryChange; - state.Temp!.EquippedChange = string.IsNullOrEmpty(equippedChange) ? "no change" : equippedChange; - _application.AI.Prompts.Variables["backstoryChange"] = state.Temp!.BackstoryChange; - _application.AI.Prompts.Variables["equippedChange"] = state.Temp!.EquippedChange; - var update = await _application.AI.CompletePromptAsync(context, state, "UpdatePlayer", null, default); - if (update == null) - { - await UpdateDMResponseAsync(context, state, ResponseGenerator.DataError()); - return false; - } - - var objString = ResponseParser.ParseJSON(update)?.FirstOrDefault(); - if (objString == null) - { - await UpdateDMResponseAsync(context, state, ResponseGenerator.DataError()); - return false; - } - - var userState = JsonSerializer.Deserialize(objString); - if (userState == null) - { - await UpdateDMResponseAsync(context, state, ResponseGenerator.DataError()); - return false; - } - - if (!string.IsNullOrWhiteSpace(userState.Backstory)) - { - state.User!.Backstory = userState.Backstory; - } - - if (!string.IsNullOrWhiteSpace(userState.Equipped)) - { - state.User!.Equipped = userState.Equipped; - } - } - - // Build message - var message = $"🤴 {state.User!.Name}"; - if (!string.IsNullOrEmpty(backstoryChange)) - { - message += $"
Backstory: {string.Join("
", state.User!.Backstory?.Split('\n') ?? Array.Empty())}"; - } - if (!string.IsNullOrEmpty(equippedChange)) - { - message += $"
Equipped: {string.Join("
", state.User!.Equipped?.Split('\n') ?? Array.Empty())}"; - } - - await context.SendActivityAsync(message); - state.Temp!.PlayerAnswered = true; - - return true; - } - } -} diff --git a/dotnet/samples/04.q.questBot/Actions/QuestAction.cs b/dotnet/samples/04.q.questBot/Actions/QuestAction.cs deleted file mode 100644 index 778f8ef24..000000000 --- a/dotnet/samples/04.q.questBot/Actions/QuestAction.cs +++ /dev/null @@ -1,142 +0,0 @@ -using Microsoft.Bot.Builder; -using Microsoft.Teams.AI.AI.Action; -using QuestBot.Models; -using QuestBot.State; - -namespace QuestBot.Actions -{ - public partial class QuestBotActions - { - [Action("quest")] - public async Task QuestAsync([ActionTurnContext] ITurnContext turnContext, [ActionTurnState] QuestState turnState, [ActionEntities] Dictionary entities) - { - ArgumentNullException.ThrowIfNull(turnContext); - ArgumentNullException.ThrowIfNull(turnState); - var data = GetData(entities); - var action = (data.Operation ?? string.Empty).Trim().ToLowerInvariant(); - switch (action) - { - case "add": - case "update": - await UpdateQuestAsync(turnContext, turnState, data); - break; - case "remove": - RemoveQuestAsync(turnState, data); - break; ; - case "finish": - FinishQuestAsync(turnState, data); - break; ; - case "list": - await ListQuestAsync(turnContext, turnState); - break; - default: - await turnContext.SendActivityAsync($"[quest.{action}]"); - break; - } - - return true; - } - - /// - /// Updates a quest. - /// - private async Task UpdateQuestAsync(ITurnContext context, QuestState state, DataEntities data) - { - var quests = - state.Conversation!.Quests == null ? - new Dictionary(StringComparer.OrdinalIgnoreCase) : - new Dictionary(state.Conversation!.Quests, StringComparer.OrdinalIgnoreCase); - - // Create new quest - var title = (data.Title ?? string.Empty).Trim(); - var quest = new Quest - { - Title = title, - Description = (data.Description ?? string.Empty).Trim(), - }; - - // Expand quest details - var details = await _application.AI.CompletePromptAsync(context, state, "QuestDetails", null, default); - if (!string.IsNullOrEmpty(details)) - { - quest.Description = details.Trim(); - } - - // Add quest to collection of active quests - quests[title] = quest; - - // Save updated location to conversation - state.Conversation.Quests = quests; - - // Tell use they have a new/updated quest - await context.SendActivityAsync(quest.ToMessage()); - state.Temp!.PlayerAnswered = true; - } - - /// - /// Removes a quest. - /// - private static bool RemoveQuestAsync(QuestState state, DataEntities data) - { - var quests = - state.Conversation!.Quests == null ? - new Dictionary(StringComparer.OrdinalIgnoreCase) : - new Dictionary(state.Conversation!.Quests, StringComparer.OrdinalIgnoreCase); - - // Find quest and delete it - var title = (data.Title ?? string.Empty).Trim(); - if (quests.Remove(title)) - { - state.Conversation!.Quests = quests; - return true; - } - - return false; - } - - /// - /// Deletes a quest from the conversation and marks the corresponding campaign objective as completed if applicable. - /// - private static void FinishQuestAsync(QuestState state, DataEntities data) - { - if (RemoveQuestAsync(state, data)) - { - // Check for the completion of a campaign objective - var campaign = state.Conversation!.Campaign; - if (campaign != null && campaign.Objectives.Count > 0) - { - var title = (data.Title ?? string.Empty).Trim(); - foreach (var objective in campaign.Objectives) - { - if (string.Equals(objective.Title, title, StringComparison.OrdinalIgnoreCase)) - { - objective.Completed = true; - break; - } - } - - state.Conversation!.Campaign = campaign; - } - } - } - - /// - /// Lists all quests in the conversation and sends them to the user. - /// - private static async Task ListQuestAsync(ITurnContext context, QuestState state) - { - if (state.Conversation!.Quests != null) - { - string message = string.Join("
", state.Conversation!.Quests.Values.Select(q => q.ToMessage())); - - // Show player list of quests - if (message.Length > 0) - { - await context.SendActivityAsync(message); - } - } - - state.Temp!.PlayerAnswered = true; - } - } -} diff --git a/dotnet/samples/04.q.questBot/Actions/QuestBotActions.cs b/dotnet/samples/04.q.questBot/Actions/QuestBotActions.cs deleted file mode 100644 index 4f50700c7..000000000 --- a/dotnet/samples/04.q.questBot/Actions/QuestBotActions.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Microsoft.Bot.Builder; -using Microsoft.Teams.AI; -using Microsoft.Teams.AI.AI; -using Microsoft.Teams.AI.AI.Action; -using Microsoft.Teams.AI.AI.Planner; -using QuestBot.Models; -using QuestBot.State; -using System.Text.Json; - -namespace QuestBot.Actions -{ - public partial class QuestBotActions - { - private readonly Application _application; - - public QuestBotActions(Application application) - { - _application = application; - } - - [Action(AIConstants.UnknownActionName)] - public async Task UnknownAsync([ActionTurnContext] ITurnContext turnContext, [ActionName] string action) - { - await turnContext.SendActivityAsync($"{action} action missing"); - return true; - } - - private static DataEntities GetData(Dictionary entities) - { - ArgumentNullException.ThrowIfNull(entities); - DataEntities data = JsonSerializer.Deserialize(JsonSerializer.Serialize(entities)) - ?? throw new ArgumentException("Action data is not data entities."); - return data; - } - - /// - /// Trims the prompt response by removing common junk that gets returned by the model. - /// - private static string TrimPromptResponse(string response) - { - // Remove common junk that gets returned by the model. - return response.Replace("DM: ", string.Empty).Replace("```", string.Empty); - } - - /// - /// Updates the conversation history with the new response and sends the response to the user. - /// - private static async Task UpdateDMResponseAsync(ITurnContext context, QuestState state, string newResponse) - { - if (ConversationHistory.GetLastLine(state).StartsWith("DM:")) - { - ConversationHistory.ReplaceLastLine(state, $"DM: {newResponse}"); - } - else - { - ConversationHistory.AddLine(state, $"DM: {newResponse}"); - } - - await context.SendActivityAsync(newResponse); - } - } -} diff --git a/dotnet/samples/04.q.questBot/Actions/StoryAction.cs b/dotnet/samples/04.q.questBot/Actions/StoryAction.cs deleted file mode 100644 index c17668ea8..000000000 --- a/dotnet/samples/04.q.questBot/Actions/StoryAction.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Microsoft.Bot.Builder; -using Microsoft.Teams.AI.AI.Action; -using QuestBot.State; - -namespace QuestBot.Actions -{ - public partial class QuestBotActions - { - [Action("story")] - public async Task StoryAsync([ActionTurnContext] ITurnContext turnContext, [ActionTurnState] QuestState turnState, [ActionEntities] Dictionary entities) - { - ArgumentNullException.ThrowIfNull(turnContext); - ArgumentNullException.ThrowIfNull(turnState); - var data = GetData(entities); - var action = (data.Operation ?? string.Empty).Trim().ToLowerInvariant(); - switch (action) - { - case "change": - case "update": - var description = (data.Description ?? string.Empty).Trim(); - if (description.Length > 0) - { - // Write story change to conversation state - turnState.Conversation!.Story = description; - } - return true; - default: - await turnContext.SendActivityAsync($"[story.{action}]"); - return true; - } - } - } -} diff --git a/dotnet/samples/04.q.questBot/Actions/TimeAction.cs b/dotnet/samples/04.q.questBot/Actions/TimeAction.cs deleted file mode 100644 index 4c9640c0e..000000000 --- a/dotnet/samples/04.q.questBot/Actions/TimeAction.cs +++ /dev/null @@ -1,152 +0,0 @@ -using Microsoft.Bot.Builder; -using Microsoft.Teams.AI.AI.Action; -using QuestBot.Models; -using QuestBot.State; - -namespace QuestBot.Actions -{ - public partial class QuestBotActions - { - [Action("time")] - public async Task TimeAsync([ActionTurnContext] ITurnContext turnContext, [ActionTurnState] QuestState turnState, [ActionEntities] Dictionary entities) - { - ArgumentNullException.ThrowIfNull(turnContext); - ArgumentNullException.ThrowIfNull(turnState); - var data = GetData(entities); - var action = (data.Operation ?? string.Empty).Trim().ToLowerInvariant(); - switch (action) - { - case "wait": - return await WaitForTimeAsync(turnContext, turnState, data); - case "query": - return await QueryTimeAsync(turnContext, turnState); - default: - await turnContext.SendActivityAsync($"[time.{action}]"); - return true; - } - } - - /// - /// Queries the current time and updates the conversation state accordingly. - /// - private static async Task QueryTimeAsync(ITurnContext context, QuestState state) - { - // Render conditions - var conditions = Conditions.DescribeConditions( - state.Conversation!.Time, - state.Conversation!.Day, - state.Conversation!.Temperature ?? string.Empty, - state.Conversation!.Weather ?? string.Empty - ); - - // Say the current conditions to the player - await UpdateDMResponseAsync(context, state, $"⏳ {conditions}"); - state.Temp!.PlayerAnswered = true; - return false; - } - - /// - /// Wait for a specified time and update the conversation state accordingly. - /// - private static async Task WaitForTimeAsync(ITurnContext context, QuestState state, DataEntities data) - { - var until = (data.Until ?? string.Empty).Trim(); - int.TryParse(data.Days, out var days); - days = days < 0 ? 0 : days; - if (!string.IsNullOrEmpty(until)) - { - string? notification = null; - state.Conversation!.Day += days; - if (string.Equals(until, Conditions.TIME_DAWN, StringComparison.OrdinalIgnoreCase)) - { - state.Conversation.Time = 4; - if (days < 2) - { - notification = "⏳ crack of dawn"; - } - } - else if (string.Equals(until, Conditions.TIME_NOON, StringComparison.OrdinalIgnoreCase)) - { - state.Conversation.Time = 12; - if (days == 0) - { - notification = "⏳ today at noon"; - } - else if (days == 1) - { - notification = "⏳ tomorrow at noon"; - } - } - else if (string.Equals(until, Conditions.TIME_AFTERNOON, StringComparison.OrdinalIgnoreCase)) - { - state.Conversation.Time = 14; - if (days == 0) - { - notification = "⏳ this afternoon"; - } - else if (days == 1) - { - notification = "⏳ tomorrow afternoon"; - } - } - else if (string.Equals(until, Conditions.TIME_EVENING, StringComparison.OrdinalIgnoreCase)) - { - state.Conversation.Time = 18; - if (days == 0) - { - notification = "⏳ this evening"; - } - else if (days == 1) - { - notification = "⏳ tomorrow evening"; - } - } - else if (string.Equals(until, Conditions.TIME_NIGHT, StringComparison.OrdinalIgnoreCase)) - { - state.Conversation.Time = 20; - if (days == 0) - { - notification = "⏳ tonight"; - } - else if (days == 1) - { - notification = "⏳ tomorrow night"; - } - } - else - { - // default to morning - state.Conversation.Time = 6; - if (days == 0) - { - notification = "⏳ early morning"; - } - else if (days == 1) - { - notification = "⏳ the next morning"; - } - } - - // Generate new weather - if (days > 0) - { - var season = Conditions.DescribeSeason(state.Conversation.Day); - state.Conversation.Temperature = Conditions.GenerateTemperature(season); - state.Conversation.Weather = Conditions.GenerateWeather(season); - state.Conversation.NextEncounterTurn = state.Conversation.Turn + (int)Math.Floor(Random.Shared.NextDouble() * 5) + 1; - } - - // Notify player - // - We don't consider this answering the players query. We want the story to be included - // for added color. - await context.SendActivityAsync(notification ?? $"⏳ {days} days later"); - return true; - } - else - { - // If the model calls "time action='wait'"" without any options, just return the current time of day. - return await QueryTimeAsync(context, state); - } - } - } -} diff --git a/dotnet/samples/04.q.questBot/AdapterWithErrorHandler.cs b/dotnet/samples/04.q.questBot/AdapterWithErrorHandler.cs deleted file mode 100644 index 439381949..000000000 --- a/dotnet/samples/04.q.questBot/AdapterWithErrorHandler.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.Bot.Builder.Integration.AspNet.Core; -using Microsoft.Bot.Builder.TraceExtensions; -using Microsoft.Bot.Connector.Authentication; - -namespace QuestBot -{ - public class AdapterWithErrorHandler : CloudAdapter - { - public AdapterWithErrorHandler(BotFrameworkAuthentication auth, ILogger logger) - : base(auth, logger) - { - OnTurnError = async (turnContext, exception) => - { - // Log any leaked exception from the application. - // NOTE: In production environment, you should consider logging this to - // Azure Application Insights. Visit https://aka.ms/bottelemetry to see how - // to add telemetry capture to your bot. - logger.LogError(exception, "[OnTurnError] unhandled error : {message}", exception.Message); - - // Send a message to the user - await turnContext.SendActivityAsync($"The bot encountered an unhandled error: {exception.Message}"); - await turnContext.SendActivityAsync("To continue to run this bot, please fix the bot source code."); - - // Send a trace activity - await turnContext.TraceActivityAsync("OnTurnError Trace", exception.Message, "https://www.botframework.com/schemas/error", "TurnError"); - }; - } - } -} diff --git a/dotnet/samples/04.q.questBot/Conditions.cs b/dotnet/samples/04.q.questBot/Conditions.cs deleted file mode 100644 index d37c1b2c8..000000000 --- a/dotnet/samples/04.q.questBot/Conditions.cs +++ /dev/null @@ -1,341 +0,0 @@ -namespace QuestBot -{ - public static class Conditions - { - public static readonly string SEASON_SPRING = "spring"; - public static readonly string SEASON_SUMMER = "summer"; - public static readonly string SEASON_FALL = "fall"; - public static readonly string SEASON_WINTER = "winter"; - - public static readonly string WEATHER_SUNNY = "sunny"; - public static readonly string WEATHER_CLOUDY = "cloudy"; - public static readonly string WEATHER_RAINY = "rainy"; - public static readonly string WEATHER_SNOWY = "snowy"; - public static readonly string WEATHER_FOGGY = "foggy"; - public static readonly string WEATHER_THUNDERSTORMS = "thunderstorms"; - public static readonly string WEATHER_WINDY = "windy"; - - public static readonly string TIME_DAWN = "dawn"; - public static readonly string TIME_MORNING = "morning"; - public static readonly string TIME_NOON = "noon"; - public static readonly string TIME_AFTERNOON = "afternoon"; - public static readonly string TIME_EVENING = "evening"; - public static readonly string TIME_NIGHT = "night"; - - public static readonly string TEMPERATURE_FREEZING = "freezing"; - public static readonly string TEMPERATURE_COLD = "cold"; - public static readonly string TEMPERATURE_COMFORTABLE = "comfortable"; - public static readonly string TEMPERATURE_WARM = "warm"; - public static readonly string TEMPERATURE_HOT = "hot"; - public static readonly string TEMPERATURE_VERYHOT = "very hot"; - - /// - /// Returns a string describing the current conditions. - /// - /// The current time. - /// The current day. - /// The current temperature. - /// The current weather. - /// A string describing the current conditions. - public static string DescribeConditions(double time, int day, string temperature, string weather) - => $"It's a {DescribeSeason(day)} {DescribeTimeOfDay(time)} and the weather is {temperature} and {weather}."; - - /// - /// Returns a string describing the time of day. - /// - /// The current time. - /// A string describing the time of day. - public static string DescribeTimeOfDay(double time) - { - if (time >= 4 && time < 6) - { - return TIME_DAWN; - } - else if (time >= 6 && time < 12) - { - return TIME_MORNING; - } - else if (time >= 12 && time < 14) - { - return TIME_NOON; - } - else if (time >= 14 && time < 18) - { - return TIME_AFTERNOON; - } - else if (time >= 18 && time < 20) - { - return TIME_EVENING; - } - else - { - return TIME_NIGHT; - } - } - - /// - /// Returns the current season based on the day of the year. - /// - /// The current day of the year. - /// The current season. - public static string DescribeSeason(int day) - { - if (day >= 79 && day <= 172) - { - return SEASON_SPRING; - } - else if (day >= 173 && day <= 265) - { - return SEASON_SUMMER; - } - else if (day >= 266 && day < 355) - { - return SEASON_FALL; - } - else - { - return SEASON_WINTER; - } - } - - /// - /// Generate random weather. - /// Weather Patterns: - /// Spring: Sunny - 40%, Cloudy - 30%, Rainy - 20%, Windy - 5%, Snowy - 2%, Foggy - 2%, Humid - 1% - /// Summer: Sunny - 60%, Cloudy - 20%, Rainy - 10%, Windy - 5%, Snowy - 2%, Foggy - 2%, Humid - 1% - /// Fall: Sunny - 40%, Cloudy - 30%, Rainy - 20%, Windy - 5%, Snowy - 5%, Foggy - 5%, Humid - 5% - /// Winter: Sunny - 20%, Cloudy - 40%, Rainy - 10%, Windy - 10%, Snowy - 10%, Foggy - 5%, Humid - 5% - /// - /// The season to generate weather for. - /// A string representing the generated weather. - public static string GenerateWeather(string season) - { - var weather = string.Empty; - var randomNumber = Random.Shared.NextDouble(); - if (string.Equals(season, SEASON_SPRING, StringComparison.OrdinalIgnoreCase)) - { - if (randomNumber < 0.4) - { - weather = WEATHER_SUNNY; - } - else if (randomNumber < 0.7) - { - weather = WEATHER_CLOUDY; - } - else if (randomNumber < 0.9) - { - weather = WEATHER_RAINY; - } - else if (randomNumber < 0.92) - { - weather = WEATHER_SNOWY; - } - else if (randomNumber < 0.94) - { - weather = WEATHER_FOGGY; - } - else - { - weather = WEATHER_THUNDERSTORMS; - } - } - else if (string.Equals(season, SEASON_SUMMER, StringComparison.OrdinalIgnoreCase)) - { - if (randomNumber < 0.6) - { - weather = WEATHER_SUNNY; - } - else if (randomNumber < 0.8) - { - weather = WEATHER_CLOUDY; - } - else if (randomNumber < 0.9) - { - weather = WEATHER_RAINY; - } - else if (randomNumber < 0.92) - { - weather = WEATHER_FOGGY; - } - else if (randomNumber < 0.94) - { - weather = WEATHER_SNOWY; - } - else - { - weather = WEATHER_THUNDERSTORMS; - } - } - else if (string.Equals(season, SEASON_FALL, StringComparison.OrdinalIgnoreCase)) - { - if (randomNumber < 0.4) - { - weather = WEATHER_SUNNY; - } - else if (randomNumber < 0.7) - { - weather = WEATHER_CLOUDY; - } - else if (randomNumber < 0.9) - { - weather = WEATHER_RAINY; - } - else if (randomNumber < 0.95) - { - weather = WEATHER_SNOWY; - } - else - { - weather = WEATHER_FOGGY; - } - } - else if (string.Equals(season, SEASON_WINTER, StringComparison.OrdinalIgnoreCase)) - { - if (randomNumber < 0.2) - { - weather = WEATHER_SUNNY; - } - else if (randomNumber < 0.65) - { - weather = WEATHER_CLOUDY; - } - else if (randomNumber < 0.8) - { - weather = WEATHER_RAINY; - } - else if (randomNumber < 0.95) - { - weather = WEATHER_SNOWY; - } - else - { - weather = WEATHER_FOGGY; - } - } - - randomNumber = Random.Shared.NextDouble(); - return randomNumber < 0.1 ? $"{weather}+{WEATHER_WINDY}" : weather; - } - - /// - /// Generates a temperature string based on the given season. - /// - /// The season to generate temperature for. - /// A string representing the generated temperature. - public static string GenerateTemperature(string season) - { - var temperature = string.Empty; - var randomNumber = Random.Shared.NextDouble(); - if (string.Equals(season, SEASON_SPRING, StringComparison.OrdinalIgnoreCase)) - { - if (randomNumber <= 0.05) - { - temperature = TEMPERATURE_FREEZING; - } - else if (randomNumber <= 0.3) - { - temperature = TEMPERATURE_COLD; - } - else if (randomNumber <= 0.8) - { - temperature = TEMPERATURE_COMFORTABLE; - } - else if (randomNumber <= 0.95) - { - temperature = TEMPERATURE_WARM; - } - else if (randomNumber <= 0.99) - { - temperature = TEMPERATURE_HOT; - } - else - { - temperature = TEMPERATURE_VERYHOT; - } - } - else if (string.Equals(season, SEASON_SUMMER, StringComparison.OrdinalIgnoreCase)) - { - if (randomNumber <= 0.01) - { - temperature = TEMPERATURE_FREEZING; - } - else if (randomNumber <= 0.1) - { - temperature = TEMPERATURE_COLD; - } - else if (randomNumber <= 0.5) - { - temperature = TEMPERATURE_COMFORTABLE; - } - else if (randomNumber <= 0.9) - { - temperature = TEMPERATURE_WARM; - } - else if (randomNumber <= 0.98) - { - temperature = TEMPERATURE_HOT; - } - else - { - temperature = TEMPERATURE_VERYHOT; - } - } - else if (string.Equals(season, SEASON_FALL, StringComparison.OrdinalIgnoreCase)) - { - if (randomNumber <= 0.1) - { - temperature = TEMPERATURE_FREEZING; - } - else if (randomNumber <= 0.4) - { - temperature = TEMPERATURE_COLD; - } - else if (randomNumber <= 0.8) - { - temperature = TEMPERATURE_COMFORTABLE; - } - else if (randomNumber <= 0.95) - { - temperature = TEMPERATURE_WARM; - } - else if (randomNumber <= 0.99) - { - temperature = TEMPERATURE_HOT; - } - else - { - temperature = TEMPERATURE_VERYHOT; - } - } - else if (string.Equals(season, SEASON_WINTER, StringComparison.OrdinalIgnoreCase)) - { - if (randomNumber <= 0.4) - { - temperature = TEMPERATURE_FREEZING; - } - else if (randomNumber <= 0.7) - { - temperature = TEMPERATURE_COLD; - } - else if (randomNumber <= 0.9) - { - temperature = TEMPERATURE_COMFORTABLE; - } - else if (randomNumber <= 0.95) - { - temperature = TEMPERATURE_WARM; - } - else if (randomNumber <= 0.99) - { - temperature = TEMPERATURE_HOT; - } - else - { - temperature = TEMPERATURE_VERYHOT; - } - } - - return temperature; - } - } -} diff --git a/dotnet/samples/04.q.questBot/Config.cs b/dotnet/samples/04.q.questBot/Config.cs deleted file mode 100644 index 9c625eec0..000000000 --- a/dotnet/samples/04.q.questBot/Config.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace QuestBot -{ - public class ConfigOptions - { - public string? BOT_ID { get; set; } - public string? BOT_PASSWORD { get; set; } - public OpenAIConfigOptions? OpenAI { get; set; } - public AzureConfigOptions? Azure { get; set; } - } - - /// - /// Options for Open AI - /// - public class OpenAIConfigOptions - { - public string? ApiKey { get; set; } - } - - /// - /// Options for Azure OpenAI - /// - public class AzureConfigOptions - { - public string? OpenAIApiKey { get; set; } - public string? OpenAIEndpoint { get; set; } - } -} diff --git a/dotnet/samples/04.q.questBot/Controllers/BotController.cs b/dotnet/samples/04.q.questBot/Controllers/BotController.cs deleted file mode 100644 index 5f80dc43a..000000000 --- a/dotnet/samples/04.q.questBot/Controllers/BotController.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.Bot.Builder; -using Microsoft.Bot.Builder.Integration.AspNet.Core; - -namespace QuestBot.Controllers -{ - [Route("api/messages")] - [ApiController] - public class BotController : ControllerBase - { - private readonly CloudAdapter _adapter; - private readonly IBot _bot; - - public BotController(CloudAdapter adapter, IBot bot) - { - _adapter = adapter; - _bot = bot; - } - - [HttpPost] - public async Task PostAsync() - { - await _adapter.ProcessAsync - ( - Request, - Response, - _bot, - cancellationToken: default - ); - } - } -} diff --git a/dotnet/samples/04.q.questBot/Models/Campaign.cs b/dotnet/samples/04.q.questBot/Models/Campaign.cs deleted file mode 100644 index 05352b370..000000000 --- a/dotnet/samples/04.q.questBot/Models/Campaign.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Text.Json.Serialization; - -namespace QuestBot.Models -{ - public class Campaign - { - [JsonPropertyName("title")] - public string Title { get; set; } = string.Empty; - - [JsonPropertyName("playerIntro")] - public string PlayerIntro { get; set; } = string.Empty; - - [JsonInclude] - [JsonPropertyName("objectives")] -#pragma warning disable CA2227 // Collection properties should be read only (public setter for JSON serializer) - public IList Objectives { get; set; } = Array.Empty(); -#pragma warning restore CA2227 // Collection properties should be read only - } -} diff --git a/dotnet/samples/04.q.questBot/Models/CampaignObjective.cs b/dotnet/samples/04.q.questBot/Models/CampaignObjective.cs deleted file mode 100644 index 2acff9c64..000000000 --- a/dotnet/samples/04.q.questBot/Models/CampaignObjective.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Text.Json.Serialization; - -namespace QuestBot.Models -{ - public class CampaignObjective - { - [JsonPropertyName("title")] - public string Title { get; set; } = string.Empty; - - [JsonPropertyName("description")] - public string Description { get; set; } = string.Empty; - - [JsonPropertyName("completed")] - public bool Completed { get; set; } - } -} diff --git a/dotnet/samples/04.q.questBot/Models/DataEntities.cs b/dotnet/samples/04.q.questBot/Models/DataEntities.cs deleted file mode 100644 index c07ef5b5a..000000000 --- a/dotnet/samples/04.q.questBot/Models/DataEntities.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Text.Json.Serialization; - -namespace QuestBot.Models -{ - public class DataEntities - { - [JsonPropertyName("operation")] - public string? Operation { get; set; } - - [JsonPropertyName("description")] - public string? Description { get; set; } - - [JsonPropertyName("items")] - public string? Items { get; set; } - - [JsonPropertyName("title")] - public string? Title { get; set; } - - [JsonPropertyName("name")] - public string? Name { get; set; } - - [JsonPropertyName("backstory")] - public string? Backstory { get; set; } - - [JsonPropertyName("equipped")] - public string? Equipped { get; set; } - - [JsonPropertyName("until")] - public string? Until { get; set; } - - [JsonPropertyName("days")] - public string? Days { get; set; } - } -} diff --git a/dotnet/samples/04.q.questBot/Models/ItemList.cs b/dotnet/samples/04.q.questBot/Models/ItemList.cs deleted file mode 100644 index 5b5c5da50..000000000 --- a/dotnet/samples/04.q.questBot/Models/ItemList.cs +++ /dev/null @@ -1,142 +0,0 @@ -namespace QuestBot.Models -{ - public class ItemList : Dictionary - { - public ItemList() : base(StringComparer.OrdinalIgnoreCase) { } - public ItemList(IEnumerable> collection) : base(collection, StringComparer.OrdinalIgnoreCase) { } - - public void AddItem(string key, int value) - { - if (ContainsKey(key)) - { - this[key] += value; - } - else - { - this[key] = value; - } - } - - /// - /// Searches for an item in an item list. - /// - /// The item to search for. - /// The best match for the item in the item list. - public string SearchItem(string item) - { - if (ContainsKey(item)) - { - return item; - } - - var bestMatch = string.Empty; - foreach (var kv in this) - { - if (kv.Key.IndexOf(item, StringComparison.OrdinalIgnoreCase) >= 0) - { - if (string.IsNullOrEmpty(bestMatch) || kv.Key.Length < bestMatch.Length) - { - bestMatch = kv.Key; - } - } - } - return bestMatch; - } - - /// - /// Convert items to normalized types / units - /// - /// The normalized items. - public ItemList NormalizeItems() - { - var normalized = new ItemList(); - foreach (var kv in this) - { - var normalizedKv = MapTo(kv.Key, kv.Value); - if (normalized.ContainsKey(normalizedKv.Key)) - { - normalized[normalizedKv.Key] += normalizedKv.Value; - } - else - { - normalized[normalizedKv.Key] = normalizedKv.Value; - } - } - - return normalized; - } - - /// - /// Converts a string of text to an item list. - /// - /// The text to convert. - /// The item list. - public static ItemList FromText(string? text) - { - var itemList = new ItemList(); - - // Parse text - text = text?.Trim()?.ToLowerInvariant(); - if (!string.IsNullOrEmpty(text)) - { - var name = string.Empty; - var parts = text.Replace('\n', ',').Split(','); - foreach (var entry in parts) - { - var pos = entry.IndexOf(':'); - if (pos >= 0) - { - // Add item to list - name += entry.Substring(0, pos); - int.TryParse(entry.Substring(pos + 1), out var count); - count = count < 0 ? 0 : count; - itemList.AddItem(name, count); - - //Next item - name = string.Empty; - } - else - { - // Append to current name - name += $",{entry}"; - } - } - - // Add dangling item - if (!string.IsNullOrEmpty(name)) - { - itemList.AddItem(name, 0); - } - } - - return itemList; - } - - /// - /// Maps an item name and count to an object. - /// - /// The item name. - /// The item count. - /// The mapped item name and count. - public static KeyValuePair MapTo(string name, int count) - { - name = name.Trim().ToLowerInvariant(); - switch (name) - { - case "coin": - case "gold coins": - case "wealth": return new KeyValuePair("gold", count); - case "coins": - case "nuggets": return new KeyValuePair("gold", count * 10); - case "": - case "item": - case "symbol": - case "symbols": - case "strange symbol": - case "strange symbols": - default: return new KeyValuePair(string.Empty, 0); - } - - } - } -} diff --git a/dotnet/samples/04.q.questBot/Models/Location.cs b/dotnet/samples/04.q.questBot/Models/Location.cs deleted file mode 100644 index 4e979c794..000000000 --- a/dotnet/samples/04.q.questBot/Models/Location.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Text.Json.Serialization; - -namespace QuestBot.Models -{ - public class Location - { - [JsonPropertyName("title")] - public string Title { get; set; } = string.Empty; - - [JsonPropertyName("description")] - public string Description { get; set; } = string.Empty; - - [JsonPropertyName("encounterChance")] - public double EncounterChance { get; set; } - } -} diff --git a/dotnet/samples/04.q.questBot/Models/Map.cs b/dotnet/samples/04.q.questBot/Models/Map.cs deleted file mode 100644 index 4c84baf91..000000000 --- a/dotnet/samples/04.q.questBot/Models/Map.cs +++ /dev/null @@ -1,257 +0,0 @@ -namespace QuestBot.Models -{ - public class Map - { - public IReadOnlyDictionary Locations { get; private set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); - - public IReadOnlyDictionary Aliases { get; private set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); - - public static readonly Map ShadowFalls = new() - { - Locations = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - ["village"] = new MapLocation - { - Id = "village", - Name = "Shadow Falls", - Description = "A bustling settlement of small homes and shops, the Village of Shadow Falls is a friendly and welcoming place.", - Details = "The small village of Shadow Falls is a bustling settlement filled with small homes, shops, and taverns. The streets are bustling with people going about their daily lives, and the smell of fresh baked goods and spices wafts through the air. At the center of town stands an old stone tower, its entrance guarded by two large stone statues. The villagers of Shadow Falls are friendly and always willing to help adventurers in need.", - EncounterChance = 0.05, - Prompt = "prompt.txt", - MapPaths = "village->S:forest\nvillage->W:lake->N:river\nvillage->E:desert\nvillage->N:mountains\nvillage->E:desert->N:canyon\nvillage->W:lake\nvillage->W:lake->N:river->E:swamp\nvillage->E:desert->E:oasis\nvillage->W:lake->N:river->N:valley\nvillage->W:lake->N:river->N:valley->E:temple\nvillage->S:forest->S:cave\nvillage->E:desert->S:pyramids", - North = "mountains", - West = "lake", - South = "forest", - East = "desert" - }, - ["forest"] = new MapLocation - { - Id = "forest", - Name = "Shadowwood Forest", - Description = "The ancient forest of Shadowwood is a sprawling wilderness full of tall trees and thick foliage.", - Details = "The ancient forest of Shadowwood is a sprawling wilderness full of tall trees and thick foliage. Wild animals roam free, and the occasional campfire or abandoned hut can be found scattered throughout the woods. The air is thick with the smell of pine, and the shadows of the trees seem to stretch on forever. There are rumors that the forest hides secrets and mysteries, but none who venture too deep ever return.", - EncounterChance = 0.4, - MapPaths = "forest->N:village\nforest->N:village->W:lake->N:river\nforest->N:village->E:desert\nforest->N:village->N:mountains\nforest->N:village->E:desert->N:canyon\nforest->N:village->W:lake\nforest->N:village->W:lake->N:river->E:swamp\nforest->N:village->E:desert->E:oasis\nforest->N:village->W:lake->N:river->N:valley\nforest->N:village->W:lake->N:river->N:valley->E:temple\nforest->S:cave\nforest->N:village->E:desert->S:pyramids", - Prompt = "prompt.txt", - North = "village", - South = "cave" - }, - ["river"] = new MapLocation - { - Id = "river", - Name = "Shadow Falls River", - Description = "A winding and treacherous path, the Shadow Falls River is a source of food for the villagers and home to dangerous creatures.", - Details = "The river that runs through Shadow Falls is a winding and treacherous path. Its waters are swift and turbulent, and it is not uncommon for travelers to become lost in its depths. The river also serves as a source of food for the villagers, as its banks are often full of fish and other aquatic life. Logs are brought in from the nearby forests, and the river is fed by giant waterfalls cascading down from the mountains. It is said that dangerous creatures lurk beneath the surface, so it is best to keep your distance.", - EncounterChance = 0.2, - MapPaths = "river->S:lake\nriver->S:lake->E:village\nriver->S:lake->E:village->S:forest\nriver->S:lake->E:village->E:desert\nriver->S:lake->E:village->N:mountains\nriver->S:lake->E:village->E:desert->N:canyon\nriver->E:swamp\nriver->S:lake->E:village->E:desert->E:oasis\nriver->N:valley\nriver->N:valley->E:temple\nriver->S:lake->E:village->S:forest->S:cave\nriver->S:lake->E:village->E:desert->S:pyramids", - Prompt = "prompt.txt", - North = "valley", - West = "swamp", - South = "lake", - East = "mountains" - }, - ["desert"] = new MapLocation - { - Id = "desert", - Name = "Desert of Shadows", - Description = "The Desert of Shadows is a vast and desolate wasteland, home to bandits and hidden secrets.", - Details = "The Desert of Shadows is a vast and desolate wasteland. The sun beats down relentlessly, and the sand is hot and dry. Cacti and other desert plants provide the only source of shade, and the occasional oasis provides respite from the heat. Bandits and other unsavory characters often lurk in the shadows, so it is best to travel in groups. Legends tell of hidden secrets and forgotten treasures buried deep in the Desert of Shadows, but none who venture too far ever return.characters often lurk in the shadows, so it is best to travel in groups.", - EncounterChance = 0.2, - MapPaths = "desert->W:village\ndesert->W:village->S:forest\ndesert->W:village->W:lake->N:river\ndesert->W:village->N:mountains\ndesert->N:canyon\ndesert->W:village->W:lake\ndesert->W:village->W:lake->N:river->E:swamp\ndesert->E:oasis\ndesert->W:village->W:lake->N:river->N:valley\ndesert->W:village->W:lake->N:river->N:valley->E:temple\ndesert->W:village->S:forest->S:cave\ndesert->S:pyramids", - Prompt = "prompt.txt", - North = "canyon", - West = "village", - South = "pyramids", - East = "oasis" - }, - ["mountains"] = new MapLocation - { - Id = "mountains", - Name = "Shadow Mountains", - Description = "The Shadow Mountains are a rugged and dangerous land, rumored to be home to dragons and other mythical creatures.", - Details = "The Shadow Mountains are a rugged and dangerous land. The peaks are high and steep, and the air is thin and cold. There are rumors of hidden caves and forgotten treasures, but none who venture too far into the Shadow Mountains ever return. It is said that dragons and other mythical creatures inhabit the highest peaks, so it is best to stay away.", - EncounterChance = 0.2, - MapPaths = "mountains->S:village\nmountains->W:river\nmountains->W:river->S:lake\nmountains->E:canyon->S:desert->E:oasis\nmountains->W:river->E:swamp\nmountains->W:river->N:valley\nmountains->W:river->N:valley->E:temple\nmountains->S:village->S:forest\nmountains->S:village->S:forest->S:cave\nmountains->E:canyon\nmountains->E:canyon->S:desert\nmountains->E:canyon->S:desert->S:pyramids", - Prompt = "prompt.txt", - West = "river", - South = "village", - East = "canyon" - }, - ["canyon"] = new MapLocation - { - Id = "canyon", - Name = "Shadow Canyon", - Description = "Shadow Canyon is a deep and treacherous ravine, the walls are steep and jagged, and secrets are hidden within.", - Details = "Shadow Canyon is a deep and treacherous ravine. The walls are steep and jagged, and the only path is a narrow, winding path. The air is musty and damp, and the occasional screech of some unseen creature echoes through the canyon. Rumor has it that an ancient civilization once resided here, but no one knows what happened to them. It is said that secrets and mysteries are hidden within the depths of the Shadow Canyon, but none who venture too far ever return.", - EncounterChance = 0.3, - MapPaths = "canyon->S:desert\ncanyon->S:desert->W:village\ncanyon->S:desert->W:village->S:forest\ncanyon->S:desert->W:village->W:lake->N:river\ncanyon->W:mountains\ncanyon->S:desert->W:village->E:desert\ncanyon->S:desert->W:village->W:lake\ncanyon->S:desert->W:village->W:lake->N:river->E:swamp\ncanyon->S:desert->E:oasis\ncanyon->S:desert->W:village->W:lake->N:river->N:valley\ncanyon->S:desert->W:village->W:lake->N:river->N:valley->E:temple\ncanyon->S:desert->W:village->S:forest->S:cave\ncanyon->S:desert->S:pyramids", - Prompt = "prompt.txt", - West = "mountains", - South = "desert" - }, - ["lake"] = new MapLocation - { - Id = "lake", - Name = "Shadow Falls Lake", - Description = "Shadow Falls Lake is a peaceful and serene body of water, home to a booming fishing and logging industry.", - Details = "Shadow Falls Lake is a peaceful and serene body of water. Its waters are crystal clear, and its shores are dotted with colorful wildflowers. A small village can be seen on the opposite shore, and the occasional boat can be seen floating by. The lake is a popular destination for those looking to relax and get away from it all, and is home to a bustling fishing and logging industry. Giant waterfalls cascade down from the nearby mountains, providing a beautiful backdrop to the lake.", - EncounterChance = 0.2, - MapPaths = "lake->E:village\nlake->E:village->S:forest\nlake->N:river\nlake->E:village->E:desert\nlake->E:village->N:mountains\nlake->E:village->E:desert->N:canyon\nlake->N:river->E:swamp\nlake->E:village->E:desert->E:oasis\nlake->N:river->N:valley\nlake->N:river->N:valley->E:temple\nlake->E:village->S:forest->S:cave\nlake->E:village->E:desert->S:pyramids", - Prompt = "prompt.txt", - North = "river", - West = "swamp", - East = "village" - }, - ["swamp"] = new MapLocation - { - Id = "swamp", - Name = "Shadow Swamp", - Description = "Shadow Swamp is a murky and treacherous marsh, home to some of the most dangerous creatures in the region.", - Details = "Shadow Swamp is a murky and treacherous marsh. The ground is soft and squishy, and the air is thick with the smell of decay. Mosquitoes and other bugs buzz about, and the occasional gator can be seen lurking in the shadows. It is best to avoid the Shadow Swamp, as it is home to some of the most dangerous creatures in the region.", - EncounterChance = 0.3, - MapPaths = "swamp->W:river\nswamp->W:river->S:lake\nswamp->W:river->S:lake->E:village\nswamp->W:river->S:lake->E:village->S:forest\nswamp->W:river->S:lake->E:village->E:desert\nswamp->W:river->S:lake->E:village->N:mountains\nswamp->W:river->S:lake->E:village->E:desert->N:canyon\nswamp->W:river->S:lake->E:village->E:desert->E:oasis\nswamp->W:river->N:valley\nswamp->W:river->N:valley->E:temple\nswamp->W:river->S:lake->E:village->S:forest->S:cave\nswamp->W:river->S:lake->E:village->E:desert->S:pyramids", - Prompt = "prompt.txt", - East = "river" - }, - ["oasis"] = new MapLocation - { - Id = "oasis", - Name = "Oasis of the Lost", - Description = "The Oasis of the Lost is a lush and vibrant paradise, full of exotic flowers and the sweet smell of coconut.", - Details = "The Oasis of the Lost is a lush and vibrant paradise. Palm trees line the shore, and the air is filled with the sweet smell of coconut. Colorful birds flit through the branches, and the waters are calm and inviting. Exotic flowers bloom in the warm rays of the sun, and the occasional frog can be heard croaking from the reeds. The oasis is a popular spot for adventurers looking to rest and recuperate before continuing their journey, and for villagers looking to cool off and escape the desert heat. However, the oasis holds secrets hidden in its depths, and those brave enough to venture into the Oasis of the Lost may find what they seek, but they do so at their own risk.", - EncounterChance = 0.1, - MapPaths = "oasis->W:desert->W:village->W:lake\noasis->W:desert->W:village\noasis->W:desert->W:village->W:river\noasis->W:desert->W:village->S:forest\noasis->W:desert->\noasis->W:desert->W:village->:mountains\noasis->W:desert->N:canyon\noasis->W:desert->W:village->W:river->E:swamp\noasis->W:desert->W:village->W:river->N:valley\noasis->W:desert->W:village->W:river->N:valley->E:temple\noasis->W:desert->W:village->S:forest->S:cave\noasis->W:desert->S:pyramids", - Prompt = "prompt.txt", - West = "desert" - }, - ["valley"] = new MapLocation - { - Id = "valley", - Name = "Valley of the Anasazi", - Description = "The Valley of the Anasazi is a mysterious and uncharted land, home to the ruins of forgotten temples.", - Details = "The Valley of the Anasazi is a mysterious and uncharted land, home to the ruins of forgotten temples and the remains of ancient civilizations. Lost in the shadows of the valley are secrets kept hidden for centuries, guarded by ancient and powerful creatures. Those brave enough to venture into the Valley of the Anasazi may find what they seek, but they do so at their own risk. The nearby Anasazi Temple is a crumbling ruin, its walls covered in vines and ancient symbols, and its corridors filled with strange echoes and whispers. It is said that the Temple holds a secret, but none who venture too deep ever return.", - EncounterChance = 0.3, - MapPaths = "valley->S:river\nvalley->S:river->S:lake\nvalley->S:river->S:lake->E:village\nvalley->S:river->S:lake->E:village->S:forest\nvalley->S:river->S:lake->E:village->E:desert\nvalley->S:river->S:lake->E:village->N:mountains\nvalley->S:river->S:lake->E:village->E:desert->N:canyon\nvalley->S:river->S:lake->N:river->E:swamp\nvalley->S:river->S:lake->E:village->E:desert->E:oasis\nvalley->E:temple\nvalley->S:river->S:lake->E:village->S:forest->S:cave\nvalley->S:river->S:lake->E:village->E:desert->S:pyramids", - Prompt = "prompt.txt", - South = "river", - East = "temple" - }, - ["temple"] = new MapLocation - { - Id = "temple", - Name = "Anasazi Temple", - Description = "The abandoned Anasazi Temple is a forgotten and crumbling ruin, its walls covered in vines and ancient symbols.", - Details = "The abandoned Anasazi Temple is a forgotten and crumbling ruin. Its walls are covered in vines, and the floor is littered with debris. Ancient symbols adorn the walls, and the air is heavy with an eerie presence. It is said that the Anasazi once inhabited this temple, and those brave enough to enter may find secrets kept hidden for centuries. But be warned, the tombs are guarded by ancient and powerful creatures, so it is best to proceed with caution.", - EncounterChance = 0.4, - MapPaths = "temple->W:valley\ntemple->W:valley->S:river\ntemple->W:valley->S:river->S:lake\ntemple->W:valley->S:river->S:lake->E:village\ntemple->W:valley->S:river->S:lake->E:village->S:forest\ntemple->W:valley->S:river->S:lake->E:village->E:desert\ntemple->W:valley->S:river->S:lake->E:village->N:mountains\ntemple->W:valley->S:river->S:lake->E:village->E:desert->N:canyon\ntemple->W:valley->S:river->S:lake->N:river->E:swamp\ntemple->W:valley->S:river->S:lake->E:village->E:desert->E:oasis\ntemple->W:valley->S:river->S:lake->E:village->S:forest->S:cave\ntemple->W:valley->S:river->S:lake->E:village->E:desert->S:pyramids", - Prompt = "prompt.txt", - West = "valley" - }, - ["cave"] = new MapLocation - { - Id = "cave", - Name = "Cave of the Ancients", - Description = "The Cave of the Ancients is a hidden and treacherous place, filled with strange echoes and whispers.", - Details = "The Cave of the Ancients is a hidden and treacherous place, difficult to find and even harder to explore. Its walls are lined with ancient symbols and runes, and its corridors are filled with strange echoes and whispers. The air is thick with the smell of musty stone and the faintest hint of something ancient and powerful. It is said that the cave holds a secret, but none who venture too deep ever return. Those brave enough to venture into the depths of the Cave of the Ancients may find what they seek, but they do so at their own risk.", - EncounterChance = 0.5, - MapPaths = "cave->N:forest\ncave->N:forest->N:village\ncave->N:forest->N:village->W:lake->N:river\ncave->N:forest->N:village->E:desert\ncave->N:forest->N:village->N:mountains\ncave->N:forest->N:village->E:desert->N:canyon\ncave->N:forest->N:village->W:lake\ncave->N:forest->N:village->W:lake->N:river->E:swamp\ncave->N:forest->N:village->E:desert->E:oasis\ncave->N:forest->N:village->W:lake->N:river->N:valley\ncave->N:forest->N:village->W:lake->N:river->N:valley->E:temple\ncave->N:forest->N:village->E:desert->S:pyramids", - Prompt = "prompt.txt", - North = "forest" - }, - ["pyramids"] = new MapLocation - { - Id = "pyramids", - Name = "Pyramids of the Forgotten", - Description = "The ancient Pyramids of the Forgotten, built by the Anuket, are home to powerful magic, guarded by ancient and powerful creatures.", - Details = "The ancient Pyramids of the Forgotten, built by the Anuket, have stood the test of time for ages. Their walls are covered in hieroglyphs, and the air is heavy with the smell of incense. It is said that these pyramids are home to powerful magic, wielded by the Anuket, and that those brave enough to enter may find great rewards. But be warned, the tombs are guarded by ancient and powerful creatures, so it is best to proceed with caution.", - EncounterChance = 0.4, - MapPaths = "pyramids->N:desert\npyramids->N:desert->W:village\npyramids->N:desert->W:village->S:forest\npyramids->N:desert->W:village->W:lake->N:river\npyramids->N:desert->W:village->N:mountains\npyramids->N:desert->N:canyon\npyramids->N:desert->W:village->W:lake\npyramids->N:desert->W:village->W:lake->N:river->E:swamp\npyramids->N:desert->E:oasis\npyramids->N:desert->W:village->W:lake->N:river->N:valley\npyramids->N:desert->W:village->W:lake->N:river->N:valley->E:temple\npyramids->N:desert->W:village->S:forest->S:cave", - Prompt = "prompt.txt", - North = "desert" - } - }, - Aliases = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - ["shadow falls"] = "village", - ["town"] = "village", - ["city"] = "village", - ["market"] = "village", - ["shops"] = "village", - ["home"] = "village", - ["base"] = "village", - ["shadowwood forest"] = "forest", - ["shadow forest"] = "forest", - ["shadow falls forest"] = "forest", - ["woods"] = "forest", - ["trees"] = "forest", - ["shadow falls river"] = "river", - ["shadow river"] = "river", - ["rivers"] = "river", - ["desert of shadows"] = "desert", - ["shadow falls desert"] = "desert", - ["shadow desert"] = "desert", - ["shadow mountains"] = "mountains", - ["shadow falls mountain"] = "mountains", - ["mountain"] = "mountain", - ["shadow canyon"] = "canyon", - ["shadow falls canyon"] = "canyon", - ["canyons"] = "canyon", - ["shadow falls lake"] = "lake", - ["shadow lake"] = "lake", - ["lakes"] = "lake", - ["shadow swamp"] = "swamp", - ["shadow fallse swamp"] = "swamp", - ["swamps"] = "swamp", - ["oasis of the lost"] = "oasis", - ["lost oasis"] = "oasis", - ["valley of the anasazi"] = "valley", - ["anasazi valley"] = "valley", - ["shadow valley"] = "valley", - ["shadow falls valley"] = "valley", - ["anasazi temple"] = "temple", - ["shadow temple"] = "temple", - ["shadow falls temple"] = "temple", - ["temples"] = "temple", - ["cave of the ancients"] = "cave", - ["shadow falls cave"] = "cave", - ["shadow cave"] = "cave", - ["hidden cave"] = "cave", - ["caves"] = "cave", - ["pyramids of the forgotten"] = "pyramids", - ["forgotten pyramids"] = "pyramids", - ["shadow pyramids"] = "pyramids", - ["shadow pyramid"] = "pyramids", - ["shadow falls pyramids"] = "pyramids", - ["shadow falls pyramid"] = "pyramids", - ["pyramid"] = "pyramids" - } - }; - - /// - /// Finds a map location by name. - /// - /// The name of the map location. - /// The map location, or null if not found. - public static MapLocation? FindMapLocation(string name) - { - var key = name?.Trim()?.ToLowerInvariant(); - if (!string.IsNullOrEmpty(key)) - { - if (key.StartsWith("the ")) - { - key = key.Substring("the ".Length); - } - - if (ShadowFalls.Aliases.ContainsKey(key)) - { - key = ShadowFalls.Aliases[key]; - } - - ShadowFalls.Locations.TryGetValue(key, out var location); - return location; - } - - return null; - } - } -} diff --git a/dotnet/samples/04.q.questBot/Models/MapLocation.cs b/dotnet/samples/04.q.questBot/Models/MapLocation.cs deleted file mode 100644 index 4e6ee809f..000000000 --- a/dotnet/samples/04.q.questBot/Models/MapLocation.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace QuestBot.Models -{ - public class MapLocation - { - public string Id { get; set; } = string.Empty; - - public string Name { get; set; } = string.Empty; - - public string Description { get; set; } = string.Empty; - - public string Details { get; set; } = string.Empty; - - public string Prompt { get; set; } = string.Empty; - - public string MapPaths { get; set; } = string.Empty; - - public double EncounterChance { get; set; } - - public string? North { get; set; } - - public string? West { get; set; } - - public string? South { get; set; } - - public string? East { get; set; } - - public string? Up { get; set; } - - public string? Down { get; set; } - } -} diff --git a/dotnet/samples/04.q.questBot/Models/Quest.cs b/dotnet/samples/04.q.questBot/Models/Quest.cs deleted file mode 100644 index 5bcd992a1..000000000 --- a/dotnet/samples/04.q.questBot/Models/Quest.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Text.Json.Serialization; - -namespace QuestBot.Models -{ - public class Quest - { - [JsonPropertyName("title")] - public string Title { get; set; } = string.Empty; - - [JsonPropertyName("description")] - public string Description { get; set; } = string.Empty; - - public string ToMessage() => $"✨ {Title}
{string.Join("
", Description.Split('\n'))}"; - } -} diff --git a/dotnet/samples/04.q.questBot/Program.cs b/dotnet/samples/04.q.questBot/Program.cs deleted file mode 100644 index e31f538c8..000000000 --- a/dotnet/samples/04.q.questBot/Program.cs +++ /dev/null @@ -1,153 +0,0 @@ -using QuestBot; - -using Microsoft.Bot.Builder; -using Microsoft.Bot.Builder.Integration.AspNet.Core; -using Microsoft.Bot.Connector.Authentication; -using Microsoft.Bot.Schema; -using Microsoft.Teams.AI; -using Microsoft.Teams.AI.AI; -using Microsoft.Teams.AI.AI.Planner; -using Microsoft.Teams.AI.AI.Prompt; -using QuestBot.State; -using QuestBot.Store; - -WebApplicationBuilder builder = WebApplication.CreateBuilder(args); - -builder.Services.AddControllers(); -builder.Services.AddHttpClient("WebClient", client => client.Timeout = TimeSpan.FromSeconds(600)); -builder.Services.AddHttpContextAccessor(); - -// Prepare Configuration for ConfigurationBotFrameworkAuthentication -ConfigOptions config = builder.Configuration.Get()!; -builder.Configuration["MicrosoftAppType"] = "MultiTenant"; -builder.Configuration["MicrosoftAppId"] = config.BOT_ID; -builder.Configuration["MicrosoftAppPassword"] = config.BOT_PASSWORD; - -// Create the Bot Framework Authentication to be used with the Bot Adapter. -builder.Services.AddSingleton(); - -// Create the Cloud Adapter with error handling enabled. -// Note: some classes expect a BotAdapter and some expect a BotFrameworkHttpAdapter, so -// register the same adapter instance for all types. -builder.Services.AddSingleton(); -builder.Services.AddSingleton(sp => sp.GetService()!); -builder.Services.AddSingleton(sp => sp.GetService()!); - -// Create singleton instances for bot application -builder.Services.AddSingleton(); - -#region Use Azure OpenAI -// Following code is for using Azure OpenAI -if (config.Azure == null - || string.IsNullOrEmpty(config.Azure.OpenAIApiKey) - || string.IsNullOrEmpty(config.Azure.OpenAIEndpoint)) -{ - throw new ArgumentException("Missing Azure configuration."); -} -builder.Services.AddSingleton(_ => new(config.Azure.OpenAIApiKey, "gpt-35-turbo", config.Azure.OpenAIEndpoint)); - -// Create the bot as a transient. In this case the ASP Controller is expecting an IBot. -builder.Services.AddTransient(sp => -{ - // Create loggers - ILoggerFactory loggerFactory = sp.GetService()!; - - // Create AzureOpenAIPlanner - IPlanner planner = new AzureOpenAIPlanner( - sp.GetService()!, - loggerFactory); - - // Create Application - AIOptions aiOptions = new( - planner: planner, - promptManager: new PromptManager("./Prompts"), - prompt: "Intro", - history: new AIHistoryOptions - { - UserPrefix = "Player:", - AssistantPrefix = "DM:", - MaxTurns = 3, - MaxTokens = 600, - TrackHistory = true - }); - ApplicationOptions ApplicationOptions = new() - { - TurnStateManager = new QuestStateManager(), - Storage = sp.GetService(), - AI = aiOptions - }; - TeamsQuestBot app = new(ApplicationOptions); - TeamsQuestBotHandlers handlers = new(app); - - // register turn and activity handlers - app.OnBeforeTurn(handlers.OnBeforeTurnAsync); - app.OnAfterTurn(handlers.OnAfterTurnAsync); - app.OnActivity(ActivityTypes.Message, handlers.OnMessageActivityAsync); - - return app; -}); -#endregion - -#region Use OpenAI -/** // Use OpenAI -if (config.OpenAI == null || string.IsNullOrEmpty(config.OpenAI.ApiKey)) -{ - throw new ArgumentException("Missing OpenAI configuration."); -} -builder.Services.AddSingleton(_ => new(config.OpenAI.ApiKey, "gpt-3.5-turbo")); - -// Create the bot as a transient. In this case the ASP Controller is expecting an IBot. -builder.Services.AddTransient(sp => -{ - // Create loggers - ILoggerFactory loggerFactory = sp.GetService()!; - - // Create OpenAIPlanner - IPlanner planner = new OpenAIPlanner( - sp.GetService()!, - loggerFactory); - - // Create Application - AIOptions aiOptions = new( - planner: planner, - promptManager: new PromptManager("./Prompts"), - prompt: "Intro", - history: new AIHistoryOptions - { - UserPrefix = "Player:", - AssistantPrefix = "DM:", - MaxTurns = 3, - MaxTokens = 600, - TrackHistory = true - }); - ApplicationOptions ApplicationOptions = new() - { - TurnStateManager = new QuestStateManager(), - Storage = sp.GetService(), - AI = aiOptions - }; - TeamsQuestBot app = new(ApplicationOptions); - TeamsQuestBotHandlers handlers = new(app); - - // register turn and activity handlers - app.OnBeforeTurn(handlers.OnBeforeTurnAsync); - app.OnAfterTurn(handlers.OnAfterTurnAsync); - app.OnActivity(ActivityTypes.Message, handlers.OnMessageActivityAsync); - - return app; -}); -**/ -#endregion - -WebApplication app = builder.Build(); - -if (app.Environment.IsDevelopment()) -{ - app.UseDeveloperExceptionPage(); -} - -app.UseStaticFiles(); -app.UseRouting(); -app.MapControllers(); - -app.Run(); diff --git a/dotnet/samples/04.q.questBot/Prompts/CreateCampaign/config.json b/dotnet/samples/04.q.questBot/Prompts/CreateCampaign/config.json deleted file mode 100644 index a466af29c..000000000 --- a/dotnet/samples/04.q.questBot/Prompts/CreateCampaign/config.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "schema": 1, - "description": "Dynamically creates a new campaign.", - "type": "completion", - "completion": { - "max_tokens": 4000, - "temperature": 0.7, - "top_p": 1.0, - "presence_penalty": 0.6, - "frequency_penalty": 0.0 - }, - "default_backends": [ - "gpt-3.5-turbo" - ] -} \ No newline at end of file diff --git a/dotnet/samples/04.q.questBot/Prompts/CreateCampaign/skprompt.txt b/dotnet/samples/04.q.questBot/Prompts/CreateCampaign/skprompt.txt deleted file mode 100644 index 30b09a3fa..000000000 --- a/dotnet/samples/04.q.questBot/Prompts/CreateCampaign/skprompt.txt +++ /dev/null @@ -1,32 +0,0 @@ -You are the dungeon master (DM) for a classic text adventure game. -The campaign is set in the world of Shadow Falls. - -These are key locations in shadow falls: - -Shadow Falls - A bustling settlement of small homes and shops, the Village of Shadow Falls is a friendly and welcoming place. -Shadowwood Forest - The ancient forest of Shadowwood is a sprawling wilderness full of tall trees and thick foliage. -Shadow Falls River - A winding and treacherous path, the Shadow Falls River is a source of food for the villagers and home to dangerous creatures. -Desert of Shadows - The Desert of Shadows is a vast and desolate wasteland, home to bandits and hidden secrets. -Shadow Mountains - The Shadow Mountains are a rugged and dangerous land, rumored to be home to dragons and other mythical creatures. -Shadow Canyon - Shadow Canyon is a deep and treacherous ravine, the walls are steep and jagged, and secrets are hidden within. -Shadow Falls Lake - Shadow Falls Lake is a peaceful and serene body of water, home to a booming fishing and logging industry. -Shadow Swamp - Shadow Swamp is a murky and treacherous marsh, home to some of the most dangerous creatures in the region. -Oasis of the Lost - The Oasis of the Lost is a lush and vibrant paradise, full of exotic flowers and the sweet smell of coconut. -Valley of the Anasazi - The Valley of the Anasazi is a mysterious and uncharted land, home to the ruins of forgotten temples. -Anasazi Temple - The abandoned Anasazi Temple is a forgotten and crumbling ruin, its walls covered in vines and ancient symbols. -Cave of the Ancients - The Cave of the Ancients is a hidden and treacherous place, filled with strange echoes and whispers. -Pyramids of the Forgotten - The ancient Pyramids of the Forgotten, built by the Anuket, are home to powerful magic, guarded by ancient and powerful creatures. - -The players begin in the village of Shadow Falls. - -Here's a JSON structure for tracking campaign objectives: - -{ - "title": "", - "playerIntro": "<500 word campaign intro>", - "objectives": [{"title": "", "description": "<300 word description>", "completed": false}] -} - -Create a campaign players with 5 objectives. -Include rewards in the descriptions for completing each objective. -Return the campaigns details using the provided structure: \ No newline at end of file diff --git a/dotnet/samples/04.q.questBot/Prompts/FindDifferences/config.json b/dotnet/samples/04.q.questBot/Prompts/FindDifferences/config.json deleted file mode 100644 index 9f99ba6e9..000000000 --- a/dotnet/samples/04.q.questBot/Prompts/FindDifferences/config.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "schema": 1, - "description": "Finds the differences between two texts.", - "type": "completion", - "completion": { - "max_tokens": 500, - "temperature": 0.0, - "top_p": 0.0, - "presence_penalty": 0.0, - "frequency_penalty": 0.0 - }, - "default_backends": [ - "text-davinci-003" - ] -} \ No newline at end of file diff --git a/dotnet/samples/04.q.questBot/Prompts/FindDifferences/skprompt.txt b/dotnet/samples/04.q.questBot/Prompts/FindDifferences/skprompt.txt deleted file mode 100644 index 7b1379094..000000000 --- a/dotnet/samples/04.q.questBot/Prompts/FindDifferences/skprompt.txt +++ /dev/null @@ -1,7 +0,0 @@ -Original Description: -{{$originalText}} - -New Location Description: -{{$newText}} - -Create a new description with just the differences: diff --git a/dotnet/samples/04.q.questBot/Prompts/Help/config.json b/dotnet/samples/04.q.questBot/Prompts/Help/config.json deleted file mode 100644 index e1165e224..000000000 --- a/dotnet/samples/04.q.questBot/Prompts/Help/config.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "schema": 1, - "description": "Answers a players request for help.", - "type": "completion", - "completion": { - "max_tokens": 1000, - "temperature": 0.7, - "top_p": 1.0, - "presence_penalty": 0.6, - "frequency_penalty": 0.0 - }, - "default_backends": [ - "gpt-3.5-turbo" - ] -} \ No newline at end of file diff --git a/dotnet/samples/04.q.questBot/Prompts/Help/skprompt.txt b/dotnet/samples/04.q.questBot/Prompts/Help/skprompt.txt deleted file mode 100644 index 156770379..000000000 --- a/dotnet/samples/04.q.questBot/Prompts/Help/skprompt.txt +++ /dev/null @@ -1,34 +0,0 @@ -You are the dungeon master DM for a classic text adventure game. -The campaign is set in the world of Shadow Falls. - -The following player actions are supported: - -- Starting and managing quests. -- Change your name, class, race, or backstory. -- Look at your map to find new locations in Shadow Falls to travel to. -- Pass time by sleeping or asking to wait a few days. -- Buy and Sell items by haggling with a shop keeper. -- Consume items by using things like potions. -- Craft new items from other items you're carrying. -- Enchant items you're carrying using spell books. -- Search for items in chests and rooms. -- Drop items to give them to other players. -- Pickup items dropped by other players. -- Dropped item live for 5 turns and then disappear. -- List items in your inventory or that have been dropped. - -Location: -{{describeLocation}} - -Story: -{{$story}} - -Conversation History: -``` - -{{$history}} - -``` - -Describe to the player the various actions they can perform in the world of Shadow Falls the way a DM would. -Give the user some context and then use a bulleted list: diff --git a/dotnet/samples/04.q.questBot/Prompts/Intro/config.json b/dotnet/samples/04.q.questBot/Prompts/Intro/config.json deleted file mode 100644 index 4d18939ca..000000000 --- a/dotnet/samples/04.q.questBot/Prompts/Intro/config.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "schema": 1, - "description": "Introduces the players to their quest.", - "type": "completion", - "completion": { - "max_tokens": 1000, - "temperature": 0.7, - "top_p": 1.0, - "presence_penalty": 0.6, - "frequency_penalty": 0.0 - }, - "default_backends": [ - "gpt-3.5-turbo" - ] -} \ No newline at end of file diff --git a/dotnet/samples/04.q.questBot/Prompts/Intro/skprompt.txt b/dotnet/samples/04.q.questBot/Prompts/Intro/skprompt.txt deleted file mode 100644 index 5079f2ac8..000000000 --- a/dotnet/samples/04.q.questBot/Prompts/Intro/skprompt.txt +++ /dev/null @@ -1,36 +0,0 @@ -You are the dungeon master DM for a classic text adventure game. -The campaign is set in the world of Shadow Falls. - -Key locations in shadow falls: - -Shadow Falls - A bustling settlement of small homes and shops, the Village of Shadow Falls is a friendly and welcoming place. -Shadowwood Forest - The ancient forest of Shadowwood is a sprawling wilderness full of tall trees and thick foliage. -Shadow Falls River - A winding and treacherous path, the Shadow Falls River is a source of food for the villagers and home to dangerous creatures. -Desert of Shadows - The Desert of Shadows is a vast and desolate wasteland, home to bandits and hidden secrets. -Shadow Mountains - The Shadow Mountains are a rugged and dangerous land, rumored to be home to dragons and other mythical creatures. -Shadow Canyon - Shadow Canyon is a deep and treacherous ravine, the walls are steep and jagged, and secrets are hidden within. -Shadow Falls Lake - Shadow Falls Lake is a peaceful and serene body of water, home to a booming fishing and logging industry. -Shadow Swamp - Shadow Swamp is a murky and treacherous marsh, home to some of the most dangerous creatures in the region. -Oasis of the Lost - The Oasis of the Lost is a lush and vibrant paradise, full of exotic flowers and the sweet smell of coconut. -Valley of the Anasazi - The Valley of the Anasazi is a mysterious and uncharted land, home to the ruins of forgotten temples. -Anasazi Temple - The abandoned Anasazi Temple is a forgotten and crumbling ruin, its walls covered in vines and ancient symbols. -Cave of the Ancients - The Cave of the Ancients is a hidden and treacherous place, filled with strange echoes and whispers. -Pyramids of the Forgotten - The ancient Pyramids of the Forgotten, built by the Anuket, are home to powerful magic, guarded by ancient and powerful creatures. - -Campaign: -{{describeCampaign}} - -Current Location: -{{describeLocation}} - -Conditions: -{{describeConditions}} - -Greet the adventurers and introduce them to the world of Shadow Falls. -Tell them about the campaign and the adventures that await them. -Suggest they explore the village of Shadow Falls while they wait for their adventure to begin. -Remind them they can ask for "help" at anytime. -- Use at least 300 words. - -Player: {{$input}} -DM: diff --git a/dotnet/samples/04.q.questBot/Prompts/ListItems/config.json b/dotnet/samples/04.q.questBot/Prompts/ListItems/config.json deleted file mode 100644 index 44b4edf2a..000000000 --- a/dotnet/samples/04.q.questBot/Prompts/ListItems/config.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "schema": 1, - "description": "Introduces the players to their quest.", - "type": "completion", - "completion": { - "max_tokens": 1000, - "temperature": 0.2, - "top_p": 1.0, - "presence_penalty": 0.0, - "frequency_penalty": 0.0 - }, - "default_backends": [ - "gpt-3.5-turbo" - ] -} \ No newline at end of file diff --git a/dotnet/samples/04.q.questBot/Prompts/ListItems/skprompt.txt b/dotnet/samples/04.q.questBot/Prompts/ListItems/skprompt.txt deleted file mode 100644 index eb5e09a1e..000000000 --- a/dotnet/samples/04.q.questBot/Prompts/ListItems/skprompt.txt +++ /dev/null @@ -1,17 +0,0 @@ -You are the dungeon master (DM) for a classic text adventure game. -The campaign is set in the world of Shadow Falls. - -Here are a list of {{$listType}} items: -{{$listItems}} - -Here's an Adaptive Card template: -{"type":"AdaptiveCard","version":"1.4","body":[{"type":"FactSet","facts":[{"title":"Sword","value":"1"}]}]} - -Player name: {{$name}} -Players query: - -``` -{{$input}} -`` - -Using the players query for context, create an adaptive card showing the items. Add an appropriate title to the card: diff --git a/dotnet/samples/04.q.questBot/Prompts/NewObjective/config.json b/dotnet/samples/04.q.questBot/Prompts/NewObjective/config.json deleted file mode 100644 index 898291e33..000000000 --- a/dotnet/samples/04.q.questBot/Prompts/NewObjective/config.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "schema": 1, - "description": "Introduces players to a new campaign objective.", - "type": "completion", - "completion": { - "max_tokens": 1000, - "temperature": 0.7, - "top_p": 1.0, - "presence_penalty": 0.0, - "frequency_penalty": 0.0 - }, - "default_backends": [ - "gpt-3.5-turbo" - ] -} \ No newline at end of file diff --git a/dotnet/samples/04.q.questBot/Prompts/NewObjective/skprompt.txt b/dotnet/samples/04.q.questBot/Prompts/NewObjective/skprompt.txt deleted file mode 100644 index addb9c7ac..000000000 --- a/dotnet/samples/04.q.questBot/Prompts/NewObjective/skprompt.txt +++ /dev/null @@ -1,53 +0,0 @@ -You are the dungeon master (DM) for a classic text adventure game. -The campaign is set in the world of Shadow Falls. - -Key locations in shadow falls: - -Shadow Falls - A bustling settlement of small homes and shops, the Village of Shadow Falls is a friendly and welcoming place. -Shadowwood Forest - The ancient forest of Shadowwood is a sprawling wilderness full of tall trees and thick foliage. -Shadow Falls River - A winding and treacherous path, the Shadow Falls River is a source of food for the villagers and home to dangerous creatures. -Desert of Shadows - The Desert of Shadows is a vast and desolate wasteland, home to bandits and hidden secrets. -Shadow Mountains - The Shadow Mountains are a rugged and dangerous land, rumored to be home to dragons and other mythical creatures. -Shadow Canyon - Shadow Canyon is a deep and treacherous ravine, the walls are steep and jagged, and secrets are hidden within. -Shadow Falls Lake - Shadow Falls Lake is a peaceful and serene body of water, home to a booming fishing and logging industry. -Shadow Swamp - Shadow Swamp is a murky and treacherous marsh, home to some of the most dangerous creatures in the region. -Oasis of the Lost - The Oasis of the Lost is a lush and vibrant paradise, full of exotic flowers and the sweet smell of coconut. -Valley of the Anasazi - The Valley of the Anasazi is a mysterious and uncharted land, home to the ruins of forgotten temples. -Anasazi Temple - The abandoned Anasazi Temple is a forgotten and crumbling ruin, its walls covered in vines and ancient symbols. -Cave of the Ancients - The Cave of the Ancients is a hidden and treacherous place, filled with strange echoes and whispers. -Pyramids of the Forgotten - The ancient Pyramids of the Forgotten, built by the Anuket, are home to powerful magic, guarded by ancient and powerful creatures. - -All Players: -{{$players}} - -Current Player Profile: -{{describePlayerInfo}} - -Game State: -{{describeGameState}} - -Campaign: -{{describeCampaign}} - -Current Quests: -{{describeQuests}} - -Current Location: -{{describeLocation}} - -Conditions: -{{describeConditions}} - -Story: -{{$story}} - -Conversation History: -``` - -{{$history}} - -``` - -A campaign objective called "{{$objectiveTitle}}" has been added as a quest. -Interrupt the player and have an NPC introduce the new quest. -- use about 200 words diff --git a/dotnet/samples/04.q.questBot/Prompts/Prompt/config.json b/dotnet/samples/04.q.questBot/Prompts/Prompt/config.json deleted file mode 100644 index e9f7a5d8b..000000000 --- a/dotnet/samples/04.q.questBot/Prompts/Prompt/config.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "schema": 1, - "description": "Main prompt for the bot.", - "type": "completion", - "completion": { - "max_tokens": 1000, - "temperature": 0.7, - "top_p": 1.0, - "presence_penalty": 0.6, - "frequency_penalty": 0.0 - }, - "default_backends": [ - "gpt-3.5-turbo" - ] -} \ No newline at end of file diff --git a/dotnet/samples/04.q.questBot/Prompts/Prompt/skprompt.txt b/dotnet/samples/04.q.questBot/Prompts/Prompt/skprompt.txt deleted file mode 100644 index 25241a123..000000000 --- a/dotnet/samples/04.q.questBot/Prompts/Prompt/skprompt.txt +++ /dev/null @@ -1,134 +0,0 @@ -You are the dungeon master (DM) for a classic text adventure game. -The campaign is set in the world of Shadow Falls. -The DM always returns the following JSON structure: - -{"type":"plan","commands":[{"type":"DO","action":"","entities":{"":}},{"type":"SAY","response":""}]} - -The following actions are supported: - -- {"type":"DO","action":"inventory","entities":{"operation": "update|list", items: ""}} -- {"type":"DO","action":"location","entities":{"operation": "change|update", title: "", description: "<100 word description>"}} -- {"type":"DO","action":"map","entities":{"operation": "query"}} -- {"type":"DO","action":"player","entities":{"operation": "update", "name"= "<name>", backstory="<100 word description>", equipped="<50 word description>"}} -- {"type":"DO","action":"quest","entities":{"operation": "add|update|remove|list|finish", title: "<title>", description: "<100 word description>"}} -- {"type":"DO","action":"story","entities":{"operation": "update", description: "<200 word description>"}} -- {"type":"DO","action":"time","entities":{"operation": "wait|query", until: "dawn|morning|noon|afternoon|evening|night", days: <count>}} - -<item list> should be formed like `<item1>:<count>,<item2>:<count>`. Examples: -"sword:1,wood:-1,stone:-1" - -<title> examples: -"Shadow Falls" - -<name> example: -"Merlin (Mage)" - -When to use actions: - -- Use `inventory operation="update"` to modify the players inventory. -- Use `inventory operation="list"` to show the player what's in their inventory. -- Use `quest operation="add"` to give the player a new quest when they ask about rumors. -- Use `quest operation="update"` to change a quest to include additional challenges or info. -- Use `quest operation="remove"` when a player decides not to accept a quest or quits it. -- Use `quest operation="finish"` when a player completes a quest. -- Use `inventory operation="update"` to give rewards for finished quests. -- Use `location operation="change"` to move players to a new location. -- Use `location operation="update"` to change the description of the current location. -- Use `map operation="query"` when players want to look at their map or ask the DM for directions. -- Use `player operation="update"` when players want to change their name or update their backstory. -- Use `story operation="update"` to update the current story to reflect the progress in their adventure. -- Use `time operation="wait"` to pass time. - - -Examples: - -`look at the map` -- map operation="query" - -`call me "Merlin (Mage)"` -- player operation="update" name="Merlin (Mage)" - -`I live in the Shadow Mountains.` -- player operation="update" backstory="I live in the Shadow Mountains" - -`I'm wearing a wizards robe and carrying a wizards staff.` -- player operation="update" equipped="Wearing a wizards robe and carrying a wizards staff." - -`craft an iron sword` -- inventory operation:"update" items:"iron sword:1,wood:-1,iron:-1" - -`what am I carrying?` -- inventory operation="list" - -`what are my quests?` -- quest operation="list" - -`what time is it?` -- time operation="query" - -`what's the weather?` -- time operation="query" - -`forget quest` -- quest operation="remove" title="<title>" - -`complete quest` -- quest operation="finish" title="<title>" -- inventory operation="update" add="gold:100" - -`finish quest` -- quest operation="finish" title=`<title>` -- inventory operation="update" add="gold:50,shield of protection:1" - -`fire an arrow at the monster` -- inventory operation="update" remove=`arrow:1` - - -Key locations in shadow falls: - -Shadow Falls - A bustling settlement of small homes and shops, the Village of Shadow Falls is a friendly and welcoming place. -Shadowwood Forest - The ancient forest of Shadowwood is a sprawling wilderness full of tall trees and thick foliage. -Shadow Falls River - A winding and treacherous path, the Shadow Falls River is a source of food for the villagers and home to dangerous creatures. -Desert of Shadows - The Desert of Shadows is a vast and desolate wasteland, home to bandits and hidden secrets. -Shadow Mountains - The Shadow Mountains are a rugged and dangerous land, rumored to be home to dragons and other mythical creatures. -Shadow Canyon - Shadow Canyon is a deep and treacherous ravine, the walls are steep and jagged, and secrets are hidden within. -Shadow Falls Lake - Shadow Falls Lake is a peaceful and serene body of water, home to a booming fishing and logging industry. -Shadow Swamp - Shadow Swamp is a murky and treacherous marsh, home to some of the most dangerous creatures in the region. -Oasis of the Lost - The Oasis of the Lost is a lush and vibrant paradise, full of exotic flowers and the sweet smell of coconut. -Valley of the Anasazi - The Valley of the Anasazi is a mysterious and uncharted land, home to the ruins of forgotten temples. -Anasazi Temple - The abandoned Anasazi Temple is a forgotten and crumbling ruin, its walls covered in vines and ancient symbols. -Cave of the Ancients - The Cave of the Ancients is a hidden and treacherous place, filled with strange echoes and whispers. -Pyramids of the Forgotten - The ancient Pyramids of the Forgotten, built by the Anuket, are home to powerful magic, guarded by ancient and powerful creatures. - -All Players: -{{$players}} - -Current Player Profile: -{{describePlayerInfo}} - -Game State: -{{describeGameState}} - -Campaign: -{{describeCampaign}} - -Current Quests: -{{describeQuests}} - -Current Location: -{{describeLocation}} - -Conditions: -{{describeConditions}} - -Story: -{{$story}} - -Instructions: - -Quests should take at least 5 turns to play out. - -Return a JSON based "plan" object that that does the following. -- {{$promptInstructions}} -- Include a `story operation="update"` action to re-write the story to include new details from the conversation. -- Only return DO/SAY commands. \ No newline at end of file diff --git a/dotnet/samples/04.q.questBot/Prompts/QuestDetails/config.json b/dotnet/samples/04.q.questBot/Prompts/QuestDetails/config.json deleted file mode 100644 index ea2626471..000000000 --- a/dotnet/samples/04.q.questBot/Prompts/QuestDetails/config.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "schema": 1, - "description": "Describes the details of a quest to the players.", - "type": "completion", - "completion": { - "max_tokens": 1000, - "temperature": 0.9, - "top_p": 1.0, - "presence_penalty": 0.0, - "frequency_penalty": 0.0 - }, - "default_backends": [ - "gpt-3.5-turbo" - ] -} \ No newline at end of file diff --git a/dotnet/samples/04.q.questBot/Prompts/QuestDetails/skprompt.txt b/dotnet/samples/04.q.questBot/Prompts/QuestDetails/skprompt.txt deleted file mode 100644 index 3aabd306e..000000000 --- a/dotnet/samples/04.q.questBot/Prompts/QuestDetails/skprompt.txt +++ /dev/null @@ -1,9 +0,0 @@ -You are the dungeon master (DM) for a classic text adventure game. -The campaign is set in the world of Shadow Falls. - -New Quests: -{{describeQuests}} - - -Elaborate on the quest details and identify any rewards for its successful completion. -Use around 100 words. diff --git a/dotnet/samples/04.q.questBot/Prompts/UpdatePlayer/config.json b/dotnet/samples/04.q.questBot/Prompts/UpdatePlayer/config.json deleted file mode 100644 index 6fa4ebfde..000000000 --- a/dotnet/samples/04.q.questBot/Prompts/UpdatePlayer/config.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "schema": 1, - "description": "Updates the players JSON object to include changes to their backstory or equipped items.", - "type": "completion", - "completion": { - "max_tokens": 1000, - "temperature": 0.7, - "top_p": 1.0, - "presence_penalty": 0.6, - "frequency_penalty": 0.0 - }, - "default_backends": [ - "gpt-3.5-turbo" - ] -} \ No newline at end of file diff --git a/dotnet/samples/04.q.questBot/Prompts/UpdatePlayer/skprompt.txt b/dotnet/samples/04.q.questBot/Prompts/UpdatePlayer/skprompt.txt deleted file mode 100644 index 700f03a5d..000000000 --- a/dotnet/samples/04.q.questBot/Prompts/UpdatePlayer/skprompt.txt +++ /dev/null @@ -1,15 +0,0 @@ -You are the dungeon master DM for a classic text adventure game. The campaign is set in the world of Shadow Falls. - -Player JSON: -{{describePlayerInfo}} - -Requested Backstory Change: -{{$backstoryChange}} - -Requested Equipped Change: -{{$equippedChange}} - -Update the players JSON object to include the requested changes above: -- use about 100 words for the backstory field. -- use about 50 words for the equipped field. -- remember key facts like the players class, race, and gender, and what they're wearing. diff --git a/dotnet/samples/04.q.questBot/Prompts/UseMap/config.json b/dotnet/samples/04.q.questBot/Prompts/UseMap/config.json deleted file mode 100644 index 1b1fa5dc0..000000000 --- a/dotnet/samples/04.q.questBot/Prompts/UseMap/config.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "schema": 1, - "description": "Answers the players questions related to the world map.", - "type": "completion", - "completion": { - "max_tokens": 1000, - "temperature": 0.7, - "top_p": 1.0, - "presence_penalty": 0.6, - "frequency_penalty": 0.0 - }, - "default_backends": [ - "gpt-3.5-turbo" - ] -} \ No newline at end of file diff --git a/dotnet/samples/04.q.questBot/Prompts/UseMap/skprompt.txt b/dotnet/samples/04.q.questBot/Prompts/UseMap/skprompt.txt deleted file mode 100644 index 3086dc2ac..000000000 --- a/dotnet/samples/04.q.questBot/Prompts/UseMap/skprompt.txt +++ /dev/null @@ -1,55 +0,0 @@ -You are the dungeon master (DM) for a classic text adventure game. -The campaign is set in the world of Shadow Falls. - -Key locations in shadow falls: - -Shadow Falls - The small village of Shadow Falls is a bustling settlement filled with small homes, shops, and taverns. The streets are bustling with people going about their daily lives, and the smell of fresh baked goods and spices wafts through the air. At the center of town stands an old stone tower, its entrance guarded by two large stone statues. The villagers of Shadow Falls are friendly and always willing to help adventurers in need. -Shadowwood Forest - The ancient forest of Shadowwood is a sprawling wilderness full of tall trees and thick foliage. Wild animals roam free, and the occasional campfire or abandoned hut can be found scattered throughout the woods. The air is thick with the smell of pine, and the shadows of the trees seem to stretch on forever. There are rumors that the forest hides secrets and mysteries, but none who venture too deep ever return. -Shadow Falls River - The river that runs through Shadow Falls is a winding and treacherous path. Its waters are swift and turbulent, and it is not uncommon for travelers to become lost in its depths. The river also serves as a source of food for the villagers, as its banks are often full of fish and other aquatic life. Logs are brought in from the nearby forests, and the river is fed by giant waterfalls cascading down from the mountains. It is said that dangerous creatures lurk beneath the surface, so it is best to keep your distance. -Desert of Shadows - The Desert of Shadows is a vast and desolate wasteland. The sun beats down relentlessly, and the sand is hot and dry. Cacti and other desert plants provide the only source of shade, and the occasional oasis provides respite from the heat. Bandits and other unsavory characters often lurk in the shadows, so it is best to travel in groups. Legends tell of hidden secrets and forgotten treasures buried deep in the Desert of Shadows, but none who venture too far ever return.characters often lurk in the shadows, so it is best to travel in groups. -Shadow Mountains - The Shadow Mountains are a rugged and dangerous land. The peaks are high and steep, and the air is thin and cold. There are rumors of hidden caves and forgotten treasures, but none who venture too far into the Shadow Mountains ever return. It is said that dragons and other mythical creatures inhabit the highest peaks, so it is best to stay away. -Shadow Canyon - Shadow Canyon is a deep and treacherous ravine. The walls are steep and jagged, and the only path is a narrow, winding path. The air is musty and damp, and the occasional screech of some unseen creature echoes through the canyon. Rumor has it that an ancient civilization once resided here, but no one knows what happened to them. It is said that secrets and mysteries are hidden within the depths of the Shadow Canyon, but none who venture too far ever return. -Shadow Falls Lake - Shadow Falls Lake is a peaceful and serene body of water. Its waters are crystal clear, and its shores are dotted with colorful wildflowers. A small village can be seen on the opposite shore, and the occasional boat can be seen floating by. The lake is a popular destination for those looking to relax and get away from it all, and is home to a bustling fishing and logging industry. Giant waterfalls cascade down from the nearby mountains, providing a beautiful backdrop to the lake. -Shadow Swamp - Shadow Swamp is a murky and treacherous marsh. The ground is soft and squishy, and the air is thick with the smell of decay. Mosquitoes and other bugs buzz about, and the occasional gator can be seen lurking in the shadows. It is best to avoid the Shadow Swamp, as it is home to some of the most dangerous creatures in the region. -Oasis of the Lost - The Oasis of the Lost is a lush and vibrant paradise. Palm trees line the shore, and the air is filled with the sweet smell of coconut. Colorful birds flit through the branches, and the waters are calm and inviting. Exotic flowers bloom in the warm rays of the sun, and the occasional frog can be heard croaking from the reeds. The oasis is a popular spot for adventurers looking to rest and recuperate before continuing their journey, and for villagers looking to cool off and escape the desert heat. However, the oasis holds secrets hidden in its depths, and those brave enough to venture into the Oasis of the Lost may find what they seek, but they do so at their own risk. -Valley of the Anasazi - The Valley of the Anasazi is a mysterious and uncharted land, home to the ruins of forgotten temples and the remains of ancient civilizations. Lost in the shadows of the valley are secrets kept hidden for centuries, guarded by ancient and powerful creatures. Those brave enough to venture into the Valley of the Anasazi may find what they seek, but they do so at their own risk. The nearby Anasazi Temple is a crumbling ruin, its walls covered in vines and ancient symbols, and its corridors filled with strange echoes and whispers. It is said that the Temple holds a secret, but none who venture too deep ever return. -Anasazi Temple - The abandoned Anasazi Temple is a forgotten and crumbling ruin. Its walls are covered in vines, and the floor is littered with debris. Ancient symbols adorn the walls, and the air is heavy with an eerie presence. It is said that the Anasazi once inhabited this temple, and those brave enough to enter may find secrets kept hidden for centuries. But be warned, the tombs are guarded by ancient and powerful creatures, so it is best to proceed with caution. -Cave of the Ancients - The Cave of the Ancients is a hidden and treacherous place, difficult to find and even harder to explore. Its walls are lined with ancient symbols and runes, and its corridors are filled with strange echoes and whispers. The air is thick with the smell of musty stone and the faintest hint of something ancient and powerful. It is said that the cave holds a secret, but none who venture too deep ever return. Those brave enough to venture into the depths of the Cave of the Ancients may find what they seek, but they do so at their own risk. -Pyramids of the Forgotten - The ancient Pyramids of the Forgotten, built by the Anuket, have stood the test of time for ages. Their walls are covered in hieroglyphs, and the air is heavy with the smell of incense. It is said that these pyramids are home to powerful magic, wielded by the Anuket, and that those brave enough to enter may find great rewards. But be warned, the tombs are guarded by ancient and powerful creatures, so it is best to proceed with caution. - -Current location: -{{describeLocation}} - -All Players: -{{$players}} - -Current Player Profile: -{{describePlayerInfo}} - -Game State: -{{describeGameState}} - -Quests: -{{describeQuests}} - -Conditions: -{{describeConditions}} - -Story: -{{$story}} - -Conversation History: -``` - -{{$history}} - -``` - -Players question: -``` - -{{$input}} - -``` - -Tell the player they look at their map and then answer the players question relative to the current location the way a DM would. Add some details: diff --git a/dotnet/samples/04.q.questBot/Properties/launchSettings.json b/dotnet/samples/04.q.questBot/Properties/launchSettings.json deleted file mode 100644 index 816f5a91f..000000000 --- a/dotnet/samples/04.q.questBot/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "profiles": { - // Debug project within Teams - "Microsoft Teams (browser)": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "launchUrl": "https://teams.microsoft.com/", - "applicationUrl": "http://localhost:5130", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "hotReloadProfile": "aspnetcore" - } - //// Uncomment following profile to debug project only (without launching Teams) - //, - //"Start Project (not in Teams)": { - // "commandName": "Project", - // "dotnetRunMessages": true, - // "applicationUrl": "https://localhost:7130;http://localhost:5130", - // "environmentVariables": { - // "ASPNETCORE_ENVIRONMENT": "Development" - // }, - // "hotReloadProfile": "aspnetcore" - //} - } -} \ No newline at end of file diff --git a/dotnet/samples/04.q.questBot/QuestBot.csproj b/dotnet/samples/04.q.questBot/QuestBot.csproj deleted file mode 100644 index 11d32b9e3..000000000 --- a/dotnet/samples/04.q.questBot/QuestBot.csproj +++ /dev/null @@ -1,47 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk.Web"> - - <PropertyGroup> - <TargetFramework>net6.0</TargetFramework> - <ImplicitUsings>enable</ImplicitUsings> - <Nullable>enable</Nullable> - </PropertyGroup> - - <!-- Use Teams Toolkit Visual Studio Extension for development --> - <ItemGroup> - <ProjectCapability Include="TeamsFx" /> - </ItemGroup> - - <ItemGroup> - <PackageReference Include="Microsoft.Bot.Builder.Azure.Blobs" Version="4.19.3" /> - <PackageReference Include="Microsoft.Bot.Builder.Integration.AspNet.Core" Version="4.21.1" /> - <PackageReference Include="Microsoft.Teams.AI" Version="1.0.*-*" /> - </ItemGroup> - - <!-- Include prompt as content in build output --> - <ItemGroup> - <Folder Include="Prompts/*/" /> - <Content Include="Prompts/*/skprompt.txt"> - <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> - <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory> - </Content> - </ItemGroup> - - <!-- Exclude Teams Toolkit files from build output, but can still be viewed from Solution Explorer --> - <ItemGroup> - <Content Remove="appPackage/**/*" /> - <None Include="appPackage/**/*" /> - <None Include="env/**/*" /> - <Content Remove="infra/**/*" /> - <None Include="infra/**/*" /> - </ItemGroup> - - <!-- Exclude local settings from publish --> - <ItemGroup> - <Content Remove="appsettings.Development.json" /> - <Content Include="appsettings.Development.json"> - <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> - <CopyToPublishDirectory>None</CopyToPublishDirectory> - </Content> - </ItemGroup> - -</Project> diff --git a/dotnet/samples/04.q.questBot/QuestBot.sln b/dotnet/samples/04.q.questBot/QuestBot.sln deleted file mode 100644 index e2b58c0ea..000000000 --- a/dotnet/samples/04.q.questBot/QuestBot.sln +++ /dev/null @@ -1,27 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.8.34004.107 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuestBot", "QuestBot.csproj", "{B3FE771B-353E-4D3D-8286-9B57A924163C}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {B3FE771B-353E-4D3D-8286-9B57A924163C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B3FE771B-353E-4D3D-8286-9B57A924163C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B3FE771B-353E-4D3D-8286-9B57A924163C}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {B3FE771B-353E-4D3D-8286-9B57A924163C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B3FE771B-353E-4D3D-8286-9B57A924163C}.Release|Any CPU.Build.0 = Release|Any CPU - {B3FE771B-353E-4D3D-8286-9B57A924163C}.Release|Any CPU.Deploy.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {D23A4B3A-DCC8-404C-BBD7-EFF9A9474DE4} - EndGlobalSection -EndGlobal diff --git a/dotnet/samples/04.q.questBot/README.md b/dotnet/samples/04.q.questBot/README.md deleted file mode 100644 index efe1f07f7..000000000 --- a/dotnet/samples/04.q.questBot/README.md +++ /dev/null @@ -1,113 +0,0 @@ -# AI in Microsoft Teams: Quest Bot - -Welcome to the Quest Bot: An epic text adventure powered by AI. The bot plays a role of the dungeon master (DM) for a classic text adventure game. Users are adventurers to explorer the campaign and achieve objectives designed by the bot. -It shows following SDK capabilities: - -<details open> - <summary><h3>Bot scaffolding</h3></summary> - Throughout the 'Program.cs' and 'TeamsQuestBot.cs' files you'll see the scaffolding created to run a bot with AI features. -</details> -<details open> - <summary><h3>Prompt engineering</h3></summary> -The 'Prompts/**/skprompt.txt' file has descriptive prompt engineering that, in plain language, instructs GPT how the bot should conduct itself at submit time. For example, in 'Help/skprompt.txt': - -#### skprompt.txt - -```text -You are the dungeon master DM for a classic text adventure game. -The campaign is set in the world of Shadow Falls. - -The following player actions are supported: -... - -{{describeLocation}} -... - -{{$story}} -... - -{{$history}} -... -``` - -- The major section ("*You are the ...*") defines the basic direction, to tell how AI should behave on human's input. -- Variable "*{{$story}}*" are set via `AI.Prompt.Variables`. -- Function "*{{describeLocation}}*" are set via `AI.Prompt.AddFunction`. -- "*{{input}}*" and "*{{history}}*" are automatically resolved from `TurnState.Temp`. - -</details> - -## Set up instructions - -All the samples in the C# .NET SDK can be set up in the same way. You can find the step by step instructions here: - [Setup Instructions](../README.md). - -Note that, this sample requires AI service so you need one more pre-step before Local Debug (F5). - -1. Set your Azure OpenAI related settings to *appsettings.Development.json*. - - ```json - "Azure": { - "OpenAIApiKey": "<your-azure-openai-api-key>", - "OpenAIEndpoint": "<your-azure-openai-endpoint>" - } - ``` - -## Interacting with the Bot - -At this point you should have set up the bot and installed it in Teams. You can interact with the bot by sending it a message. - -Here're sample interactions with the bot: - -![Start](assets/start.png) - -![Get Help](assets/help.png) - -![Move](assets/move.png) - -## Deploy to Azure - -You can use Teams Toolkit for Visual Studio or CLI to host the bot in Azure. The sample includes Bicep templates in the `/infra` directory which are used by the tools to create resources in Azure. - -You can find deployment instructions [here](../README.md#deploy-to-azure). - -Note that, this sample requires AI service so you need one more pre-step before deploy to Azure. To configure the Azure resources to have an environment variable for the Azure OpenAI Key and other settings: - -1. In `./env/.env.dev.user` file, paste your Azure OpenAI related variables. - - ```bash - SECRET_AZURE_OPENAI_API_KEY= - SECRET_AZURE_OPENAI_ENDPOINT= - ``` - -The `SECRET_` prefix is a convention used by Teams Toolkit to mask the value in any logging output and is optional. - -## Use OpenAI - -Above steps use Azure OpenAI as AI service, optionally, you can also use OpenAI as AI service. - -**As prerequisites** - -1. Prepare your own OpenAI service. -1. Modify source code `Program.cs`, comment out the "*#Use Azure OpenAI*" part, and uncomment the "*#Use OpenAI*" part. - -**For Local Debug (F5) with Teams Toolkit for Visual Studio** - -1. Set your [OpenAI API Key](https://openai.com/api/) to *appsettings.Development.json*. - - ```json - "OpenAI": { - "ApiKey": "<your-openai-api-key>" - }, - ``` - -**For Deploy to Azure with Teams Toolkit for Visual Studio** - -To configure the Azure resources to have OpenAI environment variables: - -1. In `./env/.env.dev.user` file, paste your [OpenAI API Key](https://openai.com/api/) to the environment variable `SECRET_OPENAI_KEY=`. - -## Further reading - -- [Teams Toolkit overview](https://aka.ms/vs-teams-toolkit-getting-started) -- [How Microsoft Teams bots work](https://learn.microsoft.com/azure/bot-service/bot-builder-basics-teams?view=azure-bot-service-4.0&tabs=csharp) \ No newline at end of file diff --git a/dotnet/samples/04.q.questBot/ResponseGenerator.cs b/dotnet/samples/04.q.questBot/ResponseGenerator.cs deleted file mode 100644 index db0d384bd..000000000 --- a/dotnet/samples/04.q.questBot/ResponseGenerator.cs +++ /dev/null @@ -1,163 +0,0 @@ -namespace QuestBot -{ - public static class ResponseGenerator - { - private static readonly string[] NO_QUESTS = - { - "You don't have any active quests.", - "Your quest log is blank.", - "You are without a quest currently.", - "It appears you aren't on a quest.", - "You're not on a quest." - }; - - private static readonly string[] ASK_QUEST = - { - " Ask around to find quests.", - " Villagers and barkeeps are good sources of quests.", - " Try asking if anyone has heard any rumors.", - " You can always just say \"create a new quest\".", - "" - }; - - private static readonly string[] MOVE_BLOCKED = - { - "As you begin heading towards your new destination a mysterious force transports back to your current location.", - "As you take the first steps towards what lies ahead, a strange power brings you back to where you started.", - "A mysterious source blocks your path to the next location and teleports you back to your where you're at.", - "You begin making your way towards the new destination when an unexplainable force drags you back to your current location.", - "Trust me. You are not ready to go there yet." - }; - - private static readonly string[] EMPTY_INVENTORY = - { - "It looks like you don't have any items on you.", - "It appears your inventory is devoid of any items.", - "Your inventory is completely empty.", - "You don't appear to have anything on you.", - "You have nothing in your pockets or in your bag." - }; - - private static readonly string[] EMPTY_DROPPED = - { - "You see nothing on the ground.", - "A quick scan of the area reveals nothing at your feet.", - "The immediate area is empty.", - "There's nothing here you can take. ", - "You can't see anything that can be picked up." - }; - - private static readonly string[] NOT_DROPPED = - { - "There isn't a {0} nearby.", - "There isn't a {0} on the ground.", - "Sorry, there's no {0} here.", - "It appears that a {0} is nowhere to be seen.", - "I'm sorry, but nothing like a {0} can be found." - }; - - private static readonly string[] NOT_IN_INVENTORY = - { - "You aren't carrying a {0}", - "It doesn't look like you have a {0}.", - "You can't seem to locate a {0} in your inventory.", - "Unfortunately your inventory does not have a {0}.", - "I'm sorry, you don't appear to have a {0} with you." - }; - - private static readonly string[] NOT_ENOUGH_ITEMS = - { - "You don't have enough {0}", - "I'm sorry, you don't have enough {0} for that.", - "You don't have enough {0} in your inventory.", - "That won't work. You don't have enough {0} to make it happen.", - "That's not going to work. You don't have enough {0}." - }; - - private static readonly string[] NOT_ENOUGH_GOLD = - { - "You take a deep breath as you count your golden coins. Only {0}. You're short!", - "You check your bag of coins - only {0}! A disappointingly paltry sum.", - "You empty your bag of coins, only to find {0} gold pieces. Far too few.", - "You sift through your bag, barely any coins. {0}. That wouldn't buy hardly anything of value.", - "With a sigh, you count the coins in your bag. Just {0} gold pieces, not enough." - }; - - private static readonly string[] NO_GOLD = - { - "As soon as you reach into your bag of gold coins, you can feel that something has gone seriously wrong; all your coins are gone! It looks like you've been robbed!", - "You can barely believe your eyes - all the gold coins you had in your bag are gone! Could someone have stolen them?", - "Your worst fears are realized when you open your bag of gold coins; empty :(", - "You take out your bag of gold coins, only to find that it's empty!", - "You search frantically for your bag of gold coins, but it's no where to be found." - }; - - private static readonly string[] DIRECTION_NOT_AVAILABLE_EXAMPLE = - { - "DM: You can't go {0}.\n", - "DM: There's nothing to the {0}.\n", - "DM: Travel to the {0} isn't possible.\n" - }; - - private static readonly string[] NOT_ALLOWED = - { - "You can't do that.", - "That's not going to work here.", - "Sorry... You try but it doesn't work.", - "I can't allow you to do that.", - "You tried but no dice." - }; - - private static readonly string[] DATA_ERROR = - { - "Uh oh, it looks like we've got a bit of a hiccup here. Let's try that again.", - "Oopsie! That didn't quite go as planned. Let's give it another go.", - "Oops... There was a glitch in the matrix. Let's try that again.", - "Pardon the interruption! Let's reload and give this another attempt.", - "Wow, that was unexpected! Let's do that one over." - }; - - private static readonly string[] UNKNOWN_ACTION = - { - "I'm sorry, I'm not sure how to {0}.", - "I don't know the first thing about {0}.", - "I'm not sure I'm the best person to help with {0}.", - "I'm still learning about {0}, but I'll try my best.", - "I'm afraid I'm not experienced enough with {0}." - }; - - public static string NoQuests() => string.Concat(GetRandomResponse(NO_QUESTS), GetRandomResponse(ASK_QUEST)); - - public static string MoveBlocked() => GetRandomResponse(MOVE_BLOCKED); - - public static string EmptyInventory() => GetRandomResponse(EMPTY_INVENTORY); - - public static string EmptyDropped() => GetRandomResponse(EMPTY_DROPPED); - - public static string NotDropped(string name) => string.Format(GetRandomResponse(NOT_DROPPED), name); - - public static string NotInInventory(string name) => string.Format(GetRandomResponse(NOT_IN_INVENTORY), name); - - public static string NotEnoughItems(string name) => string.Format(GetRandomResponse(NOT_ENOUGH_ITEMS), name); - - public static string NotEnoughGold(int gold) => - gold > 0 ? - string.Format(GetRandomResponse(NOT_ENOUGH_GOLD), gold) : - GetRandomResponse(NO_GOLD); - - public static string DirectionNotAvailableExample(string direction) => string.Format - ( - "Player: go {0}\n{1}", - direction, - string.Format(GetRandomResponse(DIRECTION_NOT_AVAILABLE_EXAMPLE), direction) - ); - - public static string NotAllowed() => GetRandomResponse(NOT_ALLOWED); - - public static string DataError() => GetRandomResponse(DATA_ERROR); - - public static string UnknownAction(string action) => string.Format(GetRandomResponse(UNKNOWN_ACTION), action); - - private static string GetRandomResponse(string[] responses) => responses[Random.Shared.Next(responses.Length)]; - } -} diff --git a/dotnet/samples/04.q.questBot/ResponseParser.cs b/dotnet/samples/04.q.questBot/ResponseParser.cs deleted file mode 100644 index 44d55ac4d..000000000 --- a/dotnet/samples/04.q.questBot/ResponseParser.cs +++ /dev/null @@ -1,81 +0,0 @@ -using AdaptiveCards; -using System.Text.Json; - -namespace QuestBot -{ - /// <summary> - /// Parser helper to parse JSON and Adaptive Card - /// </summary> - public static class ResponseParser - { - public static IList<string>? ParseJSON(string text) - { - int length = text.Length; - - if (length < 2) - { - return null; - } - - List<string> result = new(); - - int startIndex; - int endIndex = -1; - while (endIndex < length) - { - // Find the first "{" - startIndex = text.IndexOf('{', endIndex + 1); - if (startIndex == -1) - { - return result; - } - - // Find the first "}" such that all the contents sandwiched between S & E is a valid JSON. - endIndex = startIndex; - while (endIndex < length) - { - endIndex = text.IndexOf('}', endIndex + 1); - if (endIndex == -1) - { - return result; - } - - string possibleJSON = text.Substring(startIndex, endIndex - startIndex + 1); - - // Validate string to be a valid JSON - try - { - JsonDocument.Parse(possibleJSON); - } - catch (JsonException) - { - continue; - } - - result.Add(possibleJSON); - break; - } - } - - return result; - } - - public static AdaptiveCardParseResult? ParseAdaptiveCard(string text) - { - try - { - string? firstJSON = ParseJSON(text)?.FirstOrDefault(); - if (!string.IsNullOrEmpty(firstJSON)) - { - return AdaptiveCard.FromJson(firstJSON); - } - } - catch (InvalidOperationException) - { - // Not JSON or not a card - } - - return null; - } - } -} diff --git a/dotnet/samples/04.q.questBot/State/QuestConversationState.cs b/dotnet/samples/04.q.questBot/State/QuestConversationState.cs deleted file mode 100644 index 1c9432879..000000000 --- a/dotnet/samples/04.q.questBot/State/QuestConversationState.cs +++ /dev/null @@ -1,119 +0,0 @@ -using Microsoft.Teams.AI.State; -using QuestBot.Models; - -namespace QuestBot.State -{ - public class QuestConversationState : StateBase - { - public static readonly int CONVERSATION_STATE_VERSION = 1; - - private const string _versionKey = "versionKey"; - private const string _greetedKey = "greetedKey"; - private const string _turnKey = "turnKey"; - private const string _locationKey = "locationKey"; - private const string _locationTurnKey = "locationTurnKey"; - private const string _campaignKey = "campaignKey"; - private const string _questsKey = "questsKey"; - private const string _playersKey = "playersKey"; - private const string _timeKey = "timeKey"; - private const string _dayKey = "dayKey"; - private const string _temperatureKey = "temperatureKey"; - private const string _weatherKey = "weatherKey"; - private const string _storyKey = "storyKey"; - private const string _nextEncounterTurnKey = "nextEncounterTurnKey"; - - public int Version - { - get => Get<int>(_versionKey); - set => Set(_versionKey, value); - } - - public bool Greeted - { - get => Get<bool>(_greetedKey); - set => Set(_greetedKey, value); - } - - public int Turn - { - get => Get<int>(_turnKey); - set => Set(_turnKey, value); - } - - public Location? Location - { - get => Get<Location>(_locationKey); - set => Set(_locationKey, value); - } - - public int LocationTurn - { - get => Get<int>(_locationTurnKey); - set => Set(_locationTurnKey, value); - } - - public Campaign? Campaign - { - get => Get<Campaign>(_campaignKey); - set - { - if (value == null) - { - Remove(_campaignKey); - } - else - { - Set(_campaignKey, value); - } - } - } - - public IReadOnlyDictionary<string, Quest>? Quests - { - get => Get<IReadOnlyDictionary<string, Quest>>(_questsKey); - set => Set(_questsKey, value); - } - - public IReadOnlyList<string>? Players - { - get => Get<IReadOnlyList<string>>(_playersKey); - set => Set(_playersKey, value); - } - - public double Time - { - get => Get<double>(_timeKey); - set => Set(_timeKey, value); - } - - public int Day - { - get => Get<int>(_dayKey); - set => Set(_dayKey, value); - } - - public string? Temperature - { - get => Get<string>(_temperatureKey); - set => Set(_temperatureKey, value); - } - - public string? Weather - { - get => Get<string>(_weatherKey); - set => Set(_weatherKey, value); - } - - public string? Story - { - get => Get<string>(_storyKey); - set => Set(_storyKey, value); - } - - public int NextEncounterTurn - { - get => Get<int>(_nextEncounterTurnKey); - set => Set(_nextEncounterTurnKey, value); - } - } -} diff --git a/dotnet/samples/04.q.questBot/State/QuestState.cs b/dotnet/samples/04.q.questBot/State/QuestState.cs deleted file mode 100644 index f84acbb0d..000000000 --- a/dotnet/samples/04.q.questBot/State/QuestState.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Microsoft.Teams.AI.State; - -namespace QuestBot.State -{ - /// <summary> - /// Extend the turn state by configuring custom strongly typed state classes. - /// </summary> - public class QuestState : TurnState<QuestConversationState, QuestUserState, QuestTempState> - { - } -} diff --git a/dotnet/samples/04.q.questBot/State/QuestStateManager.cs b/dotnet/samples/04.q.questBot/State/QuestStateManager.cs deleted file mode 100644 index 6b7aa4575..000000000 --- a/dotnet/samples/04.q.questBot/State/QuestStateManager.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Microsoft.Teams.AI.State; - -namespace QuestBot.State -{ - /// <summary> - /// Shorter the class name since turn state is strongly typed. - /// </summary> - public class QuestStateManager : TurnStateManager<QuestState, QuestConversationState, QuestUserState, QuestTempState> - { - } -} diff --git a/dotnet/samples/04.q.questBot/State/QuestTempState.cs b/dotnet/samples/04.q.questBot/State/QuestTempState.cs deleted file mode 100644 index da4991788..000000000 --- a/dotnet/samples/04.q.questBot/State/QuestTempState.cs +++ /dev/null @@ -1,78 +0,0 @@ -using Microsoft.Teams.AI.State; - -namespace QuestBot.State -{ - public class QuestTempState : TempState - { - private const string _playerAnsweredKey = "playerAnsweredKey"; - private const string _promptKey = "promptKey"; - private const string _promptInstructionsKey = "promptInstructionsKey"; - private const string _listItemsKey = "listItemsKey"; - private const string _listTypeKey = "listTypeKey"; - private const string _backstoryChangeKey = "backstoryChangeKey"; - private const string _equippedChangeKey = "equippedChangeKey"; - private const string _originalTextKey = "originalTextKey"; - private const string _newTextKey = "newTextKey"; - private const string _objectiveTitleKey = "objectiveTitleKey"; - - public bool PlayerAnswered - { - get => Get<bool>(_playerAnsweredKey); - set => Set(_playerAnsweredKey, value); - } - - public string? Prompt - { - get => Get<string>(_promptKey); - set => Set(_promptKey, value); - } - - public string? PromptInstructions - { - get => Get<string>(_promptInstructionsKey); - set => Set(_promptInstructionsKey, value); - } - - public IReadOnlyDictionary<string, int>? ListItems - { - get => Get<IReadOnlyDictionary<string, int>>(_listItemsKey); - set => Set(_listItemsKey, value); - } - - public string? ListType - { - get => Get<string>(_listTypeKey); - set => Set(_listTypeKey, value); - } - - public string? BackstoryChange - { - get => Get<string>(_backstoryChangeKey); - set => Set(_backstoryChangeKey, value); - } - - public string? EquippedChange - { - get => Get<string>(_equippedChangeKey); - set => Set(_equippedChangeKey, value); - } - - public string? OriginalText - { - get => Get<string>(_originalTextKey); - set => Set(_originalTextKey, value); - } - - public string? NewText - { - get => Get<string>(_newTextKey); - set => Set(_newTextKey, value); - } - - public string? ObjectiveTitle - { - get => Get<string>(_objectiveTitleKey); - set => Set(_objectiveTitleKey, value); - } - } -} diff --git a/dotnet/samples/04.q.questBot/State/QuestUserState.cs b/dotnet/samples/04.q.questBot/State/QuestUserState.cs deleted file mode 100644 index a2dd7f5f6..000000000 --- a/dotnet/samples/04.q.questBot/State/QuestUserState.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Microsoft.Teams.AI.State; - -namespace QuestBot.State -{ - public class QuestUserState : StateBase - { - private const string _nameKey = "nameKey"; - private const string _backstoryKey = "backstoryKey"; - private const string _equippedKey = "equippedKey"; - private const string _inventoryKey = "inventoryKey"; - - public static readonly string DEFAULT_BACKSTORY = "Lives in Shadow Falls."; - public static readonly string DEFAULT_EQUIPPED = "Wearing clothes."; - - public string? Name - { - get => Get<string>(_nameKey); - set => Set(_nameKey, value); - } - - public string? Backstory - { - get => Get<string>(_backstoryKey); - set => Set(_backstoryKey, value); - } - - public string? Equipped - { - get => Get<string>(_equippedKey); - set => Set(_equippedKey, value); - } - - public IReadOnlyDictionary<string, int>? Inventory - { - get => Get<IReadOnlyDictionary<string, int>>(_inventoryKey); - set => Set(_inventoryKey, value); - } - } -} diff --git a/dotnet/samples/04.q.questBot/Store/LastWriterWinsMemoryStore.cs b/dotnet/samples/04.q.questBot/Store/LastWriterWinsMemoryStore.cs deleted file mode 100644 index b05a21f34..000000000 --- a/dotnet/samples/04.q.questBot/Store/LastWriterWinsMemoryStore.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Microsoft.Bot.Builder; - -namespace QuestBot.Store -{ - public class LastWriterWinsMemoryStore : IStorage - { - private readonly MemoryStorage _memoryStorage; - - public LastWriterWinsMemoryStore() - { - _memoryStorage = new(); - } - - public Task DeleteAsync(string[] keys, CancellationToken cancellationToken = default) - { - return _memoryStorage.DeleteAsync(keys, cancellationToken); - } - - public Task<IDictionary<string, object>> ReadAsync(string[] keys, CancellationToken cancellationToken = default) - { - return _memoryStorage.ReadAsync(keys, cancellationToken); - } - - public Task WriteAsync(IDictionary<string, object> changes, CancellationToken cancellationToken = default) - { - // Remove any eTags - foreach (var changeValue in changes.Values) - { - if (changeValue is IStoreItem item) - { - item.ETag = string.Empty; - } - } - return _memoryStorage.WriteAsync(changes, cancellationToken); - } - } -} diff --git a/dotnet/samples/04.q.questBot/Store/LastWriterWinsStore.cs b/dotnet/samples/04.q.questBot/Store/LastWriterWinsStore.cs deleted file mode 100644 index 8f7912eb3..000000000 --- a/dotnet/samples/04.q.questBot/Store/LastWriterWinsStore.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Microsoft.Bot.Builder; -using Microsoft.Bot.Builder.Azure.Blobs; - -namespace QuestBot.Store -{ - public class LastWriterWinsStore : IStorage - { - private readonly BlobsStorage _blobsStorage; - - public LastWriterWinsStore(string dataConnectionString, string containerName) - { - _blobsStorage = new(dataConnectionString, containerName); - } - - public Task DeleteAsync(string[] keys, CancellationToken cancellationToken = default) - { - return _blobsStorage.DeleteAsync(keys, cancellationToken); - } - - public Task<IDictionary<string, object>> ReadAsync(string[] keys, CancellationToken cancellationToken = default) - { - return _blobsStorage.ReadAsync(keys, cancellationToken); - } - - public Task WriteAsync(IDictionary<string, object> changes, CancellationToken cancellationToken = default) - { - // Remove any eTags - foreach (var changeValue in changes.Values) - { - if (changeValue is IStoreItem item) - { - item.ETag = string.Empty; - } - } - return _blobsStorage.WriteAsync(changes, cancellationToken); - } - } -} diff --git a/dotnet/samples/04.q.questBot/TeamsQuestBot.cs b/dotnet/samples/04.q.questBot/TeamsQuestBot.cs deleted file mode 100644 index 8d8c5b2d2..000000000 --- a/dotnet/samples/04.q.questBot/TeamsQuestBot.cs +++ /dev/null @@ -1,69 +0,0 @@ -using Microsoft.Bot.Builder; -using Microsoft.Teams.AI; -using QuestBot.Actions; -using QuestBot.State; - -namespace QuestBot -{ - public class TeamsQuestBot : Application<QuestState, QuestStateManager> - { - public TeamsQuestBot(ApplicationOptions<QuestState, QuestStateManager> options) : base(options) - { - AI.ImportActions(new QuestBotActions(this)); - - // Register prompt functions - AI.Prompts.AddFunction("describeGameState", DescribeGameState); - AI.Prompts.AddFunction("describeCampaign", DescribeCampaign); - AI.Prompts.AddFunction("describeQuests", DescribeQuests); - AI.Prompts.AddFunction("describePlayerInfo", DescribePlayerInfo); - AI.Prompts.AddFunction("describeLocation", DescribeLocation); - AI.Prompts.AddFunction("describeConditions", DescribeConditions); - } - - private Task<string> DescribeGameState(ITurnContext context, QuestState state) - { - var conversation = state.Conversation!; - return Task.FromResult($"\tTotalTurns: {conversation.Turn - 1}\n\tLocationTurns: {conversation.LocationTurn - 1}"); - } - - private Task<string> DescribeCampaign(ITurnContext _, QuestState state) - { - var conversation = state.Conversation!; - return Task.FromResult(conversation.Campaign == null ? - string.Empty : - $"\"{conversation.Campaign.Title}\" - {conversation.Campaign.PlayerIntro}"); - } - - private Task<string> DescribeQuests(ITurnContext _, QuestState state) - { - var conversation = state.Conversation!; - return Task.FromResult(conversation.Quests == null ? - "none" : - string.Join("\n\n", conversation.Quests.Values.Select(q => $"\"{q.Title}\" - {q.Description}"))); - } - - private Task<string> DescribePlayerInfo(ITurnContext _, QuestState state) - { - var player = state.User!; - var p = $"\tName: {player.Name}\n\tBackstory: {player.Backstory}\n\tEquipped: {player.Equipped}\n\tInventory:\n"; - var i = player.Inventory == null ? - string.Empty : - string.Join("\n", player.Inventory.Select(kv => $"\t\t{kv.Key}: {kv.Value}")); - return Task.FromResult(p + i); - } - - private Task<string> DescribeLocation(ITurnContext _, QuestState state) - { - var conversation = state.Conversation!; - return Task.FromResult(conversation.Location == null ? - string.Empty : - $"\"{conversation.Location.Title}\" - {conversation.Location.Description}"); - } - - private Task<string> DescribeConditions(ITurnContext _, QuestState state) - { - var conversation = state.Conversation!; - return Task.FromResult(Conditions.DescribeConditions(conversation.Time, conversation.Day, conversation.Temperature ?? string.Empty, conversation.Weather ?? string.Empty)); - } - } -} diff --git a/dotnet/samples/04.q.questBot/TeamsQuestBotHandlers.cs b/dotnet/samples/04.q.questBot/TeamsQuestBotHandlers.cs deleted file mode 100644 index 9befb6c02..000000000 --- a/dotnet/samples/04.q.questBot/TeamsQuestBotHandlers.cs +++ /dev/null @@ -1,323 +0,0 @@ -using Microsoft.Bot.Builder; -using Microsoft.Bot.Schema; -using Microsoft.Teams.AI.AI.Planner; -using QuestBot.Models; -using QuestBot.State; -using System.Text.Json; - -namespace QuestBot -{ - /// <summary> - /// Activity and Turn handlers for TeamsQuestBot - /// </summary> - public class TeamsQuestBotHandlers - { - private readonly TeamsQuestBot _app; - - public TeamsQuestBotHandlers(TeamsQuestBot app) - { - _app = app; - } - - public async Task<bool> OnBeforeTurnAsync(ITurnContext turnContext, QuestState turnState, CancellationToken cancellationToken) - { - if (!string.Equals(ActivityTypes.Message, turnContext.Activity.Type, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - - // Clear conversation state on version change - if (turnState.Conversation!.Version != QuestConversationState.CONVERSATION_STATE_VERSION) - { - turnState.ConversationStateEntry!.Delete(); - turnState.Conversation!.Version = QuestConversationState.CONVERSATION_STATE_VERSION; - } - - var conversation = turnState.Conversation!; - var player = turnState.User!; - var temp = turnState.Temp!; - - // Initialize player state - if (string.IsNullOrEmpty(player.Name)) - { - player.Name = (turnContext.Activity.From?.Name ?? string.Empty).Split(' ')[0]; - if (player.Name.Length == 0) - { - player.Name = "Adventurer"; - } - } - - if (string.IsNullOrEmpty(player.Backstory)) - { - player.Backstory = QuestUserState.DEFAULT_BACKSTORY; - } - - if (string.IsNullOrEmpty(player.Equipped)) - { - player.Equipped = QuestUserState.DEFAULT_EQUIPPED; - } - - if (player.Inventory == null) - { - player.Inventory = new ItemList - { - { "map", 1 }, - { "sword", 1 }, - { "hatchet", 1 }, - { "gold", 50 } - }; - } - - // Add player to session - if (conversation.Players != null) - { - if (!conversation.Players.Contains(player.Name)) - { - var newPlayers = new List<string>(conversation.Players); - newPlayers.Add(player.Name); - conversation.Players = newPlayers; - } - } - else - { - conversation.Players = new List<string> { player.Name }; - } - - // Update message text to include players name - // - This ensures their name is in the chat history - var useHelpPrompt = string.Equals("help", turnContext.Activity.Text.Trim(), StringComparison.OrdinalIgnoreCase); - turnContext.Activity.Text = $"[{player.Name}] {turnContext.Activity.Text}"; - - // Are we just starting? - var newDay = false; - Campaign? campaign; - Location? location; - if (!conversation.Greeted) - { - newDay = true; - conversation.Greeted = true; - temp.Prompt = "Intro"; - - // Create starting location - var village = Map.ShadowFalls.Locations["village"]; - location = new() - { - Title = village.Name, - Description = village.Details, - EncounterChance = village.EncounterChance - }; - - // Initialize conversation state - conversation.Turn = 1; - conversation.Location = location; - conversation.LocationTurn = 1; - conversation.Quests = new Dictionary<string, Quest>(StringComparer.OrdinalIgnoreCase); - conversation.Story = "The story begins."; - conversation.Day = (int)Math.Floor(Random.Shared.NextDouble() * 365) + 1; - conversation.Time = (int)Math.Floor(Random.Shared.NextDouble() * 14) + 6; // Between 6am and 8pm - conversation.NextEncounterTurn = 5 + (int)Math.Floor(Random.Shared.NextDouble() * 15); - - // Create campaign - var response = await _app.AI.CompletePromptAsync(turnContext, turnState, "CreateCampaign", null, cancellationToken); - if (string.IsNullOrEmpty(response)) - { - throw new Exception("Failed to create campaign"); - } - var campaignString = ResponseParser.ParseJSON(response)?.FirstOrDefault(); - if (campaignString != null && (campaign = JsonSerializer.Deserialize<Campaign>(campaignString)) != null) - { - conversation.Campaign = campaign; - await turnContext.SendActivityAsync($"🧙 <strong>{campaign.Title}</strong>", cancellationToken: cancellationToken); - } - else - { - turnState.ConversationStateEntry!.Delete(); - await turnContext.SendActivityAsync(ResponseGenerator.DataError(), cancellationToken: cancellationToken); - return false; - } - } - else - { - campaign = conversation.Campaign; - location = conversation.Location; - temp.Prompt = "Prompt"; - - // Increment game turn - conversation.Turn += 1; - conversation.LocationTurn += 1; - - // Pass time - conversation.Time += 0.25; - if (conversation.Time >= 24) - { - newDay = true; - conversation.Time -= 24; - conversation.Day += 1; - if (conversation.Day > 365) - { - conversation.Day = 1; - } - } - } - - // Find next campaign objective - var campaignFinished = false; - CampaignObjective? nextObjective = null; - if (campaign != null) - { - campaignFinished = true; - foreach (var objective in campaign.Objectives) - { - if (!objective.Completed) - { - // Ignore if the objective is already a quest - if (conversation.Quests == null || !conversation.Quests.ContainsKey(objective.Title)) - { - nextObjective = objective; - } - - campaignFinished = false; - break; - } - } - } - - // Is user asking for help - var objectiveAdded = false; - if (useHelpPrompt && !campaignFinished) - { - temp.Prompt = "Help"; - } - else if (nextObjective != null && Random.Shared.NextDouble() < 0.2) - { - // Add campaign objective as a quest - var newQuests = - conversation!.Quests == null ? - new Dictionary<string, Quest>(StringComparer.OrdinalIgnoreCase) : - new Dictionary<string, Quest>(conversation!.Quests, StringComparer.OrdinalIgnoreCase); - newQuests.Add(nextObjective.Title, new Quest - { - Title = nextObjective.Title, - Description = nextObjective.Description - }); - conversation.Quests = newQuests; - - // Notify user of new quest - objectiveAdded = true; - await turnContext.SendActivityAsync( - $"✨ <strong>{nextObjective.Title}</strong><br>{string.Join("<br>", nextObjective.Description.Trim().Split('\n'))}", - cancellationToken: cancellationToken); - } - - // Has a new day passed? - if (newDay) - { - var season = Conditions.DescribeSeason(conversation.Day); - conversation.Temperature = Conditions.GenerateTemperature(season); - conversation.Weather = Conditions.GenerateWeather(season); - } - - // Load temp variables for prompt use - temp.PlayerAnswered = false; - temp.PromptInstructions = "Answer the players query."; - - if (campaignFinished) - { - temp.PromptInstructions = - "The players have completed the campaign. Congratulate them and tell them they can continue adventuring or use \"/reset\" to start over with a new campaign."; - conversation.Campaign = null; - } - else if (objectiveAdded) - { - temp.Prompt = "NewObjective"; - temp.ObjectiveTitle = nextObjective!.Title; - _app.AI.Prompts.Variables["$objectiveTitle"] = temp.ObjectiveTitle; - } - else if (conversation.Turn >= conversation.NextEncounterTurn && location != null && Random.Shared.NextDouble() <= location.EncounterChance) - { - // Generate a random encounter - temp.PromptInstructions = "An encounter occurred! Describe to the player the encounter."; - conversation.NextEncounterTurn = conversation.Turn + (5 + (int)Math.Floor(Random.Shared.NextDouble() * 15)); - } - - _app.AI.Prompts.Variables["players"] = JsonSerializer.Serialize(conversation.Players ?? Array.Empty<string>()); - _app.AI.Prompts.Variables["story"] = conversation.Story ?? string.Empty; - _app.AI.Prompts.Variables["promptInstructions"] = temp.PromptInstructions ?? string.Empty; - - return true; - } - - public async Task<bool> OnAfterTurnAsync(ITurnContext turnContext, QuestState turnState, CancellationToken cancellationToken) - { - var lastSay = ConversationHistory.GetLastSay(turnState); - if (!string.IsNullOrEmpty(lastSay)) - { - // We have a dangling `DM: ` so remove it - ConversationHistory.RemoveLastLine(turnState); - - // Reply with the current story if we haven't answered player - if (!turnState.Temp!.PlayerAnswered) - { - var story = turnState.Conversation!.Story; - if (!string.IsNullOrEmpty(story)) - { - await turnContext.SendActivityAsync(story, cancellationToken: cancellationToken); - } - } - } - - return true; - } - - public async Task OnMessageActivityAsync(ITurnContext turnContext, QuestState turnState, CancellationToken cancellationToken) - { - ArgumentNullException.ThrowIfNull(turnContext); - ArgumentNullException.ThrowIfNull(turnState); - - var userInput = turnContext.Activity.Text; - - if (string.Equals("/state", userInput, StringComparison.OrdinalIgnoreCase)) - { - await turnContext.SendActivityAsync(JsonSerializer.Serialize(turnState), cancellationToken: cancellationToken); - } - else if (string.Equals("/reset-profile", userInput, StringComparison.OrdinalIgnoreCase) - || string.Equals("/reset-user", userInput, StringComparison.OrdinalIgnoreCase)) - { - turnState.UserStateEntry!.Delete(); - turnState.Conversation!.Players = Array.Empty<string>(); - await turnContext.SendActivityAsync("I've reset your profile.", cancellationToken: cancellationToken); - } - else if (string.Equals("/reset", userInput, StringComparison.OrdinalIgnoreCase)) - { - turnState.ConversationStateEntry!.Delete(); - await turnContext.SendActivityAsync("Ok lets start this over.", cancellationToken: cancellationToken); - } - else if (string.Equals("/forget", userInput, StringComparison.OrdinalIgnoreCase)) - { - ConversationHistory.Clear(turnState); - await turnContext.SendActivityAsync("Ok forgot all conversation history.", cancellationToken: cancellationToken); - } - else if (string.Equals("/history", userInput, StringComparison.OrdinalIgnoreCase)) - { - var history = ConversationHistory.ToString(turnState, 4000, "\n\n"); - await turnContext.SendActivityAsync($"<strong>Chat history:</strong><br>{history}", cancellationToken: cancellationToken); - } - else if (string.Equals("/story", userInput, StringComparison.OrdinalIgnoreCase)) - { - await turnContext.SendActivityAsync($"<strong>The story so far:</strong><br>{turnState.Conversation!.Story ?? string.Empty}", cancellationToken: cancellationToken); - } - else if (string.Equals("/profile", userInput, StringComparison.OrdinalIgnoreCase)) - { - var name = turnState.User!.Name; - var backstory = string.Join("<br>", (turnState.User!.Backstory ?? string.Empty).Split('\n')); - var equipped = string.Join("<br>", (turnState.User!.Equipped ?? string.Empty).Split('\n')); - await turnContext.SendActivityAsync($"🤴 <strong>{name}</strong><br><strong>Backstory:</strong> {backstory}<br><strong>Equipped:</strong> {equipped}", cancellationToken: cancellationToken); - } - else - { - var prompt = turnState.Temp!.Prompt; - await _app.AI.ChainAsync(turnContext, turnState, prompt, cancellationToken: cancellationToken); - } - } - } -} diff --git a/dotnet/samples/04.q.questBot/appPackage/color.png b/dotnet/samples/04.q.questBot/appPackage/color.png deleted file mode 100644 index 4ab158588..000000000 --- a/dotnet/samples/04.q.questBot/appPackage/color.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:67c7c063ba4dc41c977080c1f1fa17c897e1c72ec4a6412ed5e681b5d4cb9680 -size 1066 diff --git a/dotnet/samples/04.q.questBot/appPackage/manifest.json b/dotnet/samples/04.q.questBot/appPackage/manifest.json deleted file mode 100644 index b13d077e6..000000000 --- a/dotnet/samples/04.q.questBot/appPackage/manifest.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.16/MicrosoftTeams.schema.json", - "manifestVersion": "1.16", - "version": "1.0.0", - "id": "${{TEAMS_APP_ID}}", - "packageName": "com.package.name", - "name": { - "short": "DM-${{TEAMSFX_ENV}}", - "full": "DM" - }, - "developer": { - "name": "microsoft", - "mpnId": "", - "websiteUrl": "https://microsoft.com", - "privacyUrl": "https://privacy.microsoft.com/privacystatement", - "termsOfUseUrl": "https://www.microsoft.com/legal/terms-of-use" - }, - "description": { - "short": "An epic text adventure powered by AI.", - "full": "An epic text adventure powered by AI." - }, - "icons": { - "outline": "outline.png", - "color": "color.png" - }, - "accentColor": "#FFFFFF", - "staticTabs": [ - { - "entityId": "conversations", - "scopes": ["personal"] - }, - { - "entityId": "about", - "scopes": ["personal"] - } - ], - "bots": [ - { - "botId": "${{BOT_ID}}", - "scopes": ["personal", "team", "groupChat"], - "isNotificationOnly": false, - "supportsCalling": false, - "supportsVideo": false, - "supportsFiles": false - } - ], - "validDomains": [ - "${{BOT_DOMAIN}}" - ] -} \ No newline at end of file diff --git a/dotnet/samples/04.q.questBot/appPackage/outline.png b/dotnet/samples/04.q.questBot/appPackage/outline.png deleted file mode 100644 index 458549f6d..000000000 --- a/dotnet/samples/04.q.questBot/appPackage/outline.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b1ddc76f79027d9c0300689721649ce1f1950271a5fc4ca50ae56545228fb566 -size 249 diff --git a/dotnet/samples/04.q.questBot/appsettings.Development.json b/dotnet/samples/04.q.questBot/appsettings.Development.json deleted file mode 100644 index 12a253487..000000000 --- a/dotnet/samples/04.q.questBot/appsettings.Development.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Information", - "Microsoft.Hosting.Lifetime": "Information" - } - }, - "AllowedHosts": "*", - "BOT_ID": "$botId$", - "BOT_PASSWORD": "$bot-password$", - "Azure": { - "OpenAIApiKey": "", - "OpenAIEndpoint": "" - }, - "OpenAI": { - "ApiKey": "" - } -} \ No newline at end of file diff --git a/dotnet/samples/04.q.questBot/appsettings.json b/dotnet/samples/04.q.questBot/appsettings.json deleted file mode 100644 index 12a253487..000000000 --- a/dotnet/samples/04.q.questBot/appsettings.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Information", - "Microsoft.Hosting.Lifetime": "Information" - } - }, - "AllowedHosts": "*", - "BOT_ID": "$botId$", - "BOT_PASSWORD": "$bot-password$", - "Azure": { - "OpenAIApiKey": "", - "OpenAIEndpoint": "" - }, - "OpenAI": { - "ApiKey": "" - } -} \ No newline at end of file diff --git a/dotnet/samples/04.q.questBot/assets/help.png b/dotnet/samples/04.q.questBot/assets/help.png deleted file mode 100644 index e5bce4325..000000000 --- a/dotnet/samples/04.q.questBot/assets/help.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2524fb8a45401f4ebcff9128c7f427f0941e5eb37e30be12d206d8c5f41c5f47 -size 83277 diff --git a/dotnet/samples/04.q.questBot/assets/move.png b/dotnet/samples/04.q.questBot/assets/move.png deleted file mode 100644 index f1896b432..000000000 --- a/dotnet/samples/04.q.questBot/assets/move.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4b2ba47332b662b89d907063e2c8ecb1480fc5db8b041f21dfed3d36b569ef20 -size 40966 diff --git a/dotnet/samples/04.q.questBot/assets/start.png b/dotnet/samples/04.q.questBot/assets/start.png deleted file mode 100644 index 8c1de23db..000000000 --- a/dotnet/samples/04.q.questBot/assets/start.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:93dc04bc3d8d2698fa601608c0a347c5a3206a874649ced9b54ddcdbbaaabb99 -size 166979 diff --git a/dotnet/samples/04.q.questBot/env/.env.dev b/dotnet/samples/04.q.questBot/env/.env.dev deleted file mode 100644 index 4261152ab..000000000 --- a/dotnet/samples/04.q.questBot/env/.env.dev +++ /dev/null @@ -1,16 +0,0 @@ -# This file includes environment variables that will be committed to git by default. - -# Built-in environment variables -TEAMSFX_ENV=dev - -# Updating AZURE_SUBSCRIPTION_ID or AZURE_RESOURCE_GROUP_NAME after provision may also require an update to RESOURCE_SUFFIX, because some services require a globally unique name across subscriptions/resource groups. -AZURE_SUBSCRIPTION_ID= -AZURE_RESOURCE_GROUP_NAME= -RESOURCE_SUFFIX= - -# Generated during provision, you can also add your own variables. -BOT_ID= -TEAMS_APP_ID= -TEAMS_APP_TENANT_ID= -BOT_AZURE_APP_SERVICE_RESOURCE_ID= -BOT_DOMAIN= \ No newline at end of file diff --git a/dotnet/samples/04.q.questBot/env/.env.dev.user b/dotnet/samples/04.q.questBot/env/.env.dev.user deleted file mode 100644 index d9a8c24c6..000000000 --- a/dotnet/samples/04.q.questBot/env/.env.dev.user +++ /dev/null @@ -1,12 +0,0 @@ -# This file includes environment variables that will not be committed to git by default. You can set these environment variables in your CI/CD system for your project. - -# If you're adding a secret value, add SECRET_ prefix to the name so Teams Toolkit can handle them properly -# Secrets. Keys prefixed with `SECRET_` will be masked in Teams Toolkit logs. -SECRET_BOT_PASSWORD= - -# Azure OpenAI settings -SECRET_AZURE_OPENAI_API_KEY=<azure-openai-api-key> -SECRET_AZURE_OPENAI_ENDPOINT=<azure-openai-endpoint> - -# OpenAI settings -SECRET_OPENAI_API_KEY=<openai-api-key> \ No newline at end of file diff --git a/dotnet/samples/04.q.questBot/env/.env.local b/dotnet/samples/04.q.questBot/env/.env.local deleted file mode 100644 index 689f23843..000000000 --- a/dotnet/samples/04.q.questBot/env/.env.local +++ /dev/null @@ -1,12 +0,0 @@ -# This file includes environment variables that can be committed to git. It's gitignored by default because it represents your local development environment. - -# Built-in environment variables -TEAMSFX_ENV=local - -# Generated during provision, you can also add your own variables. -BOT_ID= -TEAMS_APP_ID= -TEAMS_APP_TENANT_ID= -TEAMSFX_M365_USER_NAME= -BOT_DOMAIN= -BOT_ENDPOINT= \ No newline at end of file diff --git a/dotnet/samples/04.q.questBot/infra/azure.bicep b/dotnet/samples/04.q.questBot/infra/azure.bicep deleted file mode 100644 index 2ca6dde7b..000000000 --- a/dotnet/samples/04.q.questBot/infra/azure.bicep +++ /dev/null @@ -1,99 +0,0 @@ -@maxLength(20) -@minLength(4) -@description('Used to generate names for all resources in this file') -param resourceBaseName string - -@description('Required when create Azure Bot service') -param botAadAppClientId string - -@secure() -@description('Required by Bot Framework package in your bot project') -param botAadAppClientSecret string - -@secure() -@description('The OpenAI API Key to be added to App Service Settings') -param openAIApiKey string - -@secure() -@description('The Azure OpenAI API Key to be added to App Service Settings') -param azureOpenAIApiKey string - -@secure() -@description('The Azure OpenAI Endpoint to be added to App Service Settings') -param azureOpenAIEndpoint string - -param webAppSKU string - -@maxLength(42) -param botDisplayName string - -param serverfarmsName string = resourceBaseName -param webAppName string = resourceBaseName -param location string = resourceGroup().location - -// Compute resources for your Web App -resource serverfarm 'Microsoft.Web/serverfarms@2021-02-01' = { - kind: 'app' - location: location - name: serverfarmsName - sku: { - name: webAppSKU - } -} - -// Web App that hosts your bot -resource webApp 'Microsoft.Web/sites@2021-02-01' = { - kind: 'app' - location: location - name: webAppName - properties: { - serverFarmId: serverfarm.id - httpsOnly: true - siteConfig: { - alwaysOn: true - appSettings: [ - { - name: 'WEBSITE_RUN_FROM_PACKAGE' - value: '1' // Run Azure APP Service from a package file - } - { - name: 'BOT_ID' - value: botAadAppClientId - } - { - name: 'BOT_PASSWORD' - value: botAadAppClientSecret - } - // ASP.NET Core treats double underscore (__) as colon (:) to support hierarchical keys - { - name: 'OpenAI__ApiKey' - value: openAIApiKey - } - { - name: 'Azure__OpenAIApiKey' - value: azureOpenAIApiKey - } - { - name: 'Azure__OpenAIEndpoint' - value: azureOpenAIEndpoint - } - ] - ftpsState: 'FtpsOnly' - } - } -} - -// Register your web service as a bot with the Bot Framework -module azureBotRegistration './botRegistration/azurebot.bicep' = { - name: 'Azure-Bot-registration' - params: { - resourceBaseName: resourceBaseName - botAadAppClientId: botAadAppClientId - botAppDomain: webApp.properties.defaultHostName - botDisplayName: botDisplayName - } -} - -// The output will be persisted in .env.{envName}. Visit https://aka.ms/teamsfx-actions/arm-deploy for more details. -output BOT_AZURE_APP_SERVICE_RESOURCE_ID string = webApp.id -output BOT_DOMAIN string = webApp.properties.defaultHostName diff --git a/dotnet/samples/04.q.questBot/infra/azure.parameters.json b/dotnet/samples/04.q.questBot/infra/azure.parameters.json deleted file mode 100644 index 386ad301d..000000000 --- a/dotnet/samples/04.q.questBot/infra/azure.parameters.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "resourceBaseName": { - "value": "bot${{RESOURCE_SUFFIX}}" - }, - "botAadAppClientId": { - "value": "${{BOT_ID}}" - }, - "botAadAppClientSecret": { - "value": "${{SECRET_BOT_PASSWORD}}" - }, - "webAppSKU": { - "value": "B1" - }, - "botDisplayName": { - "value": "QuestBotInfra" - }, - "openAIApiKey": { - "value": "${{SECRET_OPENAI_API_KEY}}" - }, - "azureOpenAIApiKey": { - "value": "${{SECRET_AZURE_OPENAI_API_KEY}}" - }, - "azureOpenAIEndpoint": { - "value": "${{SECRET_AZURE_OPENAI_ENDPOINT}}" - } - } - } \ No newline at end of file diff --git a/dotnet/samples/04.q.questBot/infra/botRegistration/azurebot.bicep b/dotnet/samples/04.q.questBot/infra/botRegistration/azurebot.bicep deleted file mode 100644 index ab67c7a56..000000000 --- a/dotnet/samples/04.q.questBot/infra/botRegistration/azurebot.bicep +++ /dev/null @@ -1,37 +0,0 @@ -@maxLength(20) -@minLength(4) -@description('Used to generate names for all resources in this file') -param resourceBaseName string - -@maxLength(42) -param botDisplayName string - -param botServiceName string = resourceBaseName -param botServiceSku string = 'F0' -param botAadAppClientId string -param botAppDomain string - -// Register your web service as a bot with the Bot Framework -resource botService 'Microsoft.BotService/botServices@2021-03-01' = { - kind: 'azurebot' - location: 'global' - name: botServiceName - properties: { - displayName: botDisplayName - endpoint: 'https://${botAppDomain}/api/messages' - msaAppId: botAadAppClientId - } - sku: { - name: botServiceSku - } -} - -// Connect the bot service to Microsoft Teams -resource botServiceMsTeamsChannel 'Microsoft.BotService/botServices/channels@2021-03-01' = { - parent: botService - location: 'global' - name: 'MsTeamsChannel' - properties: { - channelName: 'MsTeamsChannel' - } -} diff --git a/dotnet/samples/04.q.questBot/infra/botRegistration/readme.md b/dotnet/samples/04.q.questBot/infra/botRegistration/readme.md deleted file mode 100644 index d5416243c..000000000 --- a/dotnet/samples/04.q.questBot/infra/botRegistration/readme.md +++ /dev/null @@ -1 +0,0 @@ -The `azurebot.bicep` module is provided to help you create Azure Bot service when you don't use Azure to host your app. If you use Azure as infrastrcture for your app, `azure.bicep` under infra folder already leverages this module to create Azure Bot service for you. You don't need to deploy `azurebot.bicep` again. \ No newline at end of file diff --git a/dotnet/samples/04.q.questBot/teamsapp.local.yml b/dotnet/samples/04.q.questBot/teamsapp.local.yml deleted file mode 100644 index 0a8c74b5c..000000000 --- a/dotnet/samples/04.q.questBot/teamsapp.local.yml +++ /dev/null @@ -1,86 +0,0 @@ -# yaml-language-server: $schema=https://aka.ms/teams-toolkit/1.1.0/yaml.schema.json -# Visit https://aka.ms/teamsfx-v5.0-guide for details on this file -# Visit https://aka.ms/teamsfx-actions for details on actions -version: 1.1.0 - -provision: - # Creates a Teams app - - uses: teamsApp/create - with: - # Teams app name - name: QuestBot-${{TEAMSFX_ENV}} - # Write the information of created resources into environment file for - # the specified environment variable(s). - writeToEnvironmentFile: - teamsAppId: TEAMS_APP_ID - - # Create or reuse an existing Azure Active Directory application for bot. - - uses: botAadApp/create - with: - # The Azure Active Directory application's display name - name: QuestBot-${{TEAMSFX_ENV}} - writeToEnvironmentFile: - # The Azure Active Directory application's client id created for bot. - botId: BOT_ID - # The Azure Active Directory application's client secret created for bot. - botPassword: SECRET_BOT_PASSWORD - - # Generate runtime appsettings to JSON file - - uses: file/createOrUpdateJsonFile - with: - target: ./appsettings.Development.json - content: - BOT_ID: ${{BOT_ID}} - BOT_PASSWORD: ${{SECRET_BOT_PASSWORD}} - - # Create or update the bot registration on dev.botframework.com - - uses: botFramework/create - with: - botId: ${{BOT_ID}} - name: QuestBot - messagingEndpoint: ${{BOT_ENDPOINT}}/api/messages - description: "" - channels: - - name: msteams - - # Validate using manifest schema - - uses: teamsApp/validateManifest - with: - # Path to manifest template - manifestPath: ./appPackage/manifest.json - # Build Teams app package with latest env value - - uses: teamsApp/zipAppPackage - with: - # Path to manifest template - manifestPath: ./appPackage/manifest.json - outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip - outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json - # Validate app package using validation rules - - uses: teamsApp/validateAppPackage - with: - # Relative path to this file. This is the path for built zip file. - appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip - - # Apply the Teams app manifest to an existing Teams app in - # Teams Developer Portal. - # Will use the app id in manifest file to determine which Teams app to update. - - uses: teamsApp/update - with: - # Relative path to this file. This is the path for built zip file. - appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip - - # Create or update debug profile in lauchsettings file - - uses: file/createOrUpdateJsonFile - with: - target: ./Properties/launchSettings.json - content: - profiles: - Microsoft Teams (browser): - commandName: "Project" - dotnetRunMessages: true - launchBrowser: true - launchUrl: "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&appTenantId=${{TEAMS_APP_TENANT_ID}}&login_hint=${{TEAMSFX_M365_USER_NAME}}" - applicationUrl: "http://localhost:5130" - environmentVariables: - ASPNETCORE_ENVIRONMENT: "Development" - hotReloadProfile: "aspnetcore" \ No newline at end of file diff --git a/dotnet/samples/04.q.questBot/teamsapp.yml b/dotnet/samples/04.q.questBot/teamsapp.yml deleted file mode 100644 index 3563d1f65..000000000 --- a/dotnet/samples/04.q.questBot/teamsapp.yml +++ /dev/null @@ -1,96 +0,0 @@ -# yaml-language-server: $schema=https://aka.ms/teams-toolkit/1.1.0/yaml.schema.json -# Visit https://aka.ms/teamsfx-v5.0-guide for details on this file -# Visit https://aka.ms/teamsfx-actions for details on actions -version: 1.1.0 - -environmentFolderPath: ./env - -# Triggered when 'teamsfx provision' is executed -provision: - # Creates a Teams app - - uses: teamsApp/create - with: - # Teams app name - name: QuestBot-${{TEAMSFX_ENV}} - # Write the information of created resources into environment file for - # the specified environment variable(s). - writeToEnvironmentFile: - teamsAppId: TEAMS_APP_ID - - # Create or reuse an existing Azure Active Directory application for bot. - - uses: botAadApp/create - with: - # The Azure Active Directory application's display name - name: QuestBot-${{TEAMSFX_ENV}} - writeToEnvironmentFile: - # The Azure Active Directory application's client id created for bot. - botId: BOT_ID - # The Azure Active Directory application's client secret created for bot. - botPassword: SECRET_BOT_PASSWORD - - - uses: arm/deploy # Deploy given ARM templates parallelly. - with: - # AZURE_SUBSCRIPTION_ID is a built-in environment variable, - # if its value is empty, TeamsFx will prompt you to select a subscription. - # Referencing other environment variables with empty values - # will skip the subscription selection prompt. - subscriptionId: ${{AZURE_SUBSCRIPTION_ID}} - # AZURE_RESOURCE_GROUP_NAME is a built-in environment variable, - # if its value is empty, TeamsFx will prompt you to select or create one - # resource group. - # Referencing other environment variables with empty values - # will skip the resource group selection prompt. - resourceGroupName: ${{AZURE_RESOURCE_GROUP_NAME}} - templates: - - path: ./infra/azure.bicep # Relative path to this file - # Relative path to this yaml file. - # Placeholders will be replaced with corresponding environment - # variable before ARM deployment. - parameters: ./infra/azure.parameters.json - # Required when deploying ARM template - deploymentName: Create-resources-for-bot - # Teams Toolkit will download this bicep CLI version from github for you, - # will use bicep CLI in PATH if you remove this config. - bicepCliVersion: v0.9.1 - - # Validate using manifest schema - - uses: teamsApp/validateManifest - with: - # Path to manifest template - manifestPath: ./appPackage/manifest.json - # Build Teams app package with latest env value - - uses: teamsApp/zipAppPackage - with: - # Path to manifest template - manifestPath: ./appPackage/manifest.json - outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip - outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json - # Validate app package using validation rules - - uses: teamsApp/validateAppPackage - with: - # Relative path to this file. This is the path for built zip file. - appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip - # Apply the Teams app manifest to an existing Teams app in - # Teams Developer Portal. - # Will use the app id in manifest file to determine which Teams app to update. - - uses: teamsApp/update - with: - # Relative path to this file. This is the path for built zip file. - appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip - -# Triggered when 'teamsfx deploy' is executed -deploy: - - uses: cli/runDotnetCommand - with: - args: publish --configuration Release --runtime win-x86 --self-contained - # Deploy your application to Azure App Service using the zip deploy feature. - # For additional details, refer to https://aka.ms/zip-deploy-to-app-services. - - uses: azureAppService/zipDeploy - with: - # deploy base folder - artifactFolder: bin/Release/net6.0/win-x86/publish - # The resource id of the cloud resource to be deployed to. - # This key will be generated by arm/deploy action automatically. - # You can replace it with your existing Azure Resource id - # or add it to your environment variable file. - resourceId: ${{BOT_AZURE_APP_SERVICE_RESOURCE_ID}}