diff --git a/Samples/Samples.BotBuilder/Program.cs b/Samples/Samples.BotBuilder/Program.cs index ac8046a4..5acc54e1 100644 --- a/Samples/Samples.BotBuilder/Program.cs +++ b/Samples/Samples.BotBuilder/Program.cs @@ -1,39 +1,25 @@ using Microsoft.Bot.Builder.Integration.AspNet.Core; -using Microsoft.Teams.Api.Activities; -using Microsoft.Teams.Apps; using Microsoft.Teams.Apps.Activities; -using Microsoft.Teams.Apps.Annotations; using Microsoft.Teams.Apps.Extensions; using Microsoft.Teams.Plugins.AspNetCore.DevTools.Extensions; using Microsoft.Teams.Plugins.AspNetCore.Extensions; -namespace Samples.BotBuilder; +using Samples.BotBuilder; -public static partial class Program -{ - public static void Main(string[] args) - { - var builder = WebApplication.CreateBuilder(args); - builder.Services.AddTransient(); - builder - .AddTeams() - .AddTeamsDevTools() - .AddBotBuilder(); +var builder = WebApplication.CreateBuilder(args); +builder + .AddTeams() + .AddTeamsDevTools() + .AddBotBuilder(); + +var app = builder.Build(); - var app = builder.Build(); +var teams = app.UseTeams(); - app.UseTeams(); - app.Run(); - } +teams.OnMessage(async context => +{ + await context.Typing(); + await context.Send("hi from teams..."); +}); - [TeamsController] - public class Controller - { - [Message] - public async Task OnMessage([Context] MessageActivity activity, [Context] IContext.Client client, [Context] Microsoft.Teams.Common.Logging.ILogger log) - { - await client.Typing(); - await client.Send($"hi from teams..."); - } - } -} \ No newline at end of file +app.Run(); diff --git a/Samples/Samples.Cards/Program.cs b/Samples/Samples.Cards/Program.cs index a198d4e7..d7dbf6d2 100644 --- a/Samples/Samples.Cards/Program.cs +++ b/Samples/Samples.Cards/Program.cs @@ -2,514 +2,357 @@ using Microsoft.Teams.Api.Activities.Invokes; using Microsoft.Teams.Api.AdaptiveCards; -using Microsoft.Teams.Apps; using Microsoft.Teams.Apps.Activities; -using Microsoft.Teams.Apps.Annotations; +using Microsoft.Teams.Apps.Activities.Invokes; using Microsoft.Teams.Apps.Extensions; -using Microsoft.Teams.Apps.Plugins; using Microsoft.Teams.Cards; using Microsoft.Teams.Common; using Microsoft.Teams.Plugins.AspNetCore.DevTools.Extensions; using Microsoft.Teams.Plugins.AspNetCore.Extensions; -namespace Samples.Cards; +var builder = WebApplication.CreateBuilder(args); +builder.WebHost.UseUrls("http://localhost:3978"); +builder.Services.AddOpenApi(); +builder.AddTeams().AddTeamsDevTools(); -public static partial class Program +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) { - public static void Main(string[] args) - { - var builder = WebApplication.CreateBuilder(args); - // Bind the application to localhost:3978 when run with `dotnet run` - builder.WebHost.UseUrls("http://localhost:3978"); - builder.Services.AddOpenApi(); - builder.Services.AddTransient(); - builder.AddTeams().AddTeamsDevTools(); + app.MapOpenApi(); +} - var app = builder.Build(); +app.UseHttpsRedirection(); +var teams = app.UseTeams(); - if (app.Environment.IsDevelopment()) - { - app.MapOpenApi(); - } +teams.OnMessage(async context => +{ + var activity = context.Activity; + context.Log.Info($"[MESSAGE] Received: {SanitizeForLog(activity.Text)}"); + context.Log.Info($"[MESSAGE] From: {SanitizeForLog(activity.From?.Name ?? "unknown")}"); - app.UseHttpsRedirection(); - app.UseTeams(); - app.Run(); - } + var text = activity.Text?.ToLowerInvariant() ?? ""; - [TeamsController] - public class Controller + if (text.Contains("card")) { - [Message] - public async Task OnMessage([Context] Microsoft.Teams.Api.Activities.MessageActivity activity, [Context] IContext.Client client, [Context] Microsoft.Teams.Common.Logging.ILogger log) - { - log.Info($"[MESSAGE] Received: {SanitizeForLog(activity.Text)}"); - log.Info($"[MESSAGE] From: {SanitizeForLog(activity.From?.Name ?? "unknown")}"); + context.Log.Info("[CARD] Basic card requested"); + var card = CreateBasicAdaptiveCard(); + await context.Send(card); + } + else if (text.Contains("profile")) + { + context.Log.Info("[PROFILE] Profile card requested"); + var card = CreateProfileCard(); + await context.Send(card); + } + else if (text.Contains("validation")) + { + context.Log.Info("[VALIDATION] Validation card requested"); + var card = CreateProfileCardWithValidation(); + await context.Send(card); + } + else if (text.Contains("feedback")) + { + context.Log.Info("[FEEDBACK] Feedback card requested"); + var card = CreateFeedbackCard(); + await context.Send(card); + } + else if (text.Contains("form")) + { + context.Log.Info("[FORM] Task form card requested"); + var card = CreateTaskFormCard(); + await context.Send(card); + } + else if (text.Contains("json")) + { + context.Log.Info("[JSON] JSON deserialization card requested"); + var card = CreateCardFromJson(); + await context.Send(card); + } + else if (text.Contains("reply")) + { + await context.Send("Hello! How can I assist you today?"); + } + else + { + await context.Typing(); + await context.Send($"You said '{activity.Text}'. Try typing: card, profile, validation, feedback, form, json, or reply"); + } +}); - var text = activity.Text?.ToLowerInvariant() ?? ""; +teams.OnAdaptiveCardAction(async context => +{ + var activity = context.Activity; + context.Log.Info("[CARD_ACTION] Card action received"); - if (text.Contains("card")) - { - log.Info("[CARD] Basic card requested"); - var card = CreateBasicAdaptiveCard(); - await client.Send(card); - } - else if (text.Contains("profile")) - { - log.Info("[PROFILE] Profile card requested"); - var card = CreateProfileCard(); - await client.Send(card); - } - else if (text.Contains("validation")) - { - log.Info("[VALIDATION] Validation card requested"); - var card = CreateProfileCardWithValidation(); - await client.Send(card); - } - else if (text.Contains("feedback")) - { - log.Info("[FEEDBACK] Feedback card requested"); - var card = CreateFeedbackCard(); - await client.Send(card); - } - else if (text.Contains("form")) - { - log.Info("[FORM] Task form card requested"); - var card = CreateTaskFormCard(); - await client.Send(card); - } - else if (text.Contains("json")) - { - log.Info("[JSON] JSON deserialization card requested"); - var card = CreateCardFromJson(); - await client.Send(card); - } - else if (text.Contains("reply")) - { - await client.Send("Hello! How can I assist you today?"); - } - else - { - await client.Typing(); - await client.Send($"You said '{activity.Text}'. Try typing: card, profile, validation, feedback, form, json, or reply"); - } - } + var data = activity.Value?.Action?.Data; - [Microsoft.Teams.Apps.Activities.Invokes.AdaptiveCard.Action] - public async Task OnCardAction([Context] AdaptiveCards.ActionActivity activity, [Context] IContext.Client client, [Context] Microsoft.Teams.Common.Logging.ILogger log) - { - log.Info("[CARD_ACTION] Card action received"); + context.Log.Info($"[CARD_ACTION] Raw data: {JsonSerializer.Serialize(data)}"); - var data = activity.Value?.Action?.Data; + if (data == null) + { + context.Log.Error("[CARD_ACTION] No data in card action"); + return new ActionResponse.Message("No data specified") { StatusCode = 400 }; + } - // Let's log the actual data structure to understand what we're working with - log.Info($"[CARD_ACTION] Raw data: {System.Text.Json.JsonSerializer.Serialize(data)}"); + string? action = data.TryGetValue("action", out var actionObj) ? actionObj?.ToString() : null; - if (data == null) - { - log.Error("[CARD_ACTION] No data in card action"); - return new ActionResponse.Message("No data specified") { StatusCode = 400 }; - } + if (string.IsNullOrEmpty(action)) + { + context.Log.Error("[CARD_ACTION] No action specified in card data"); + return new ActionResponse.Message("No action specified") { StatusCode = 400 }; + } + context.Log.Info($"[CARD_ACTION] Processing action: {action}"); - // Extract action from the Value property - string? action = data.TryGetValue("action", out var actionObj) ? actionObj?.ToString() : null; + string? GetFormValue(string key) + { + if (data.TryGetValue(key, out var val)) + { + if (val is JsonElement element) + return element.GetString(); + return val?.ToString(); + } + return null; + } - if (string.IsNullOrEmpty(action)) - { - log.Error("[CARD_ACTION] No action specified in card data"); - return new ActionResponse.Message("No action specified") { StatusCode = 400 }; - } - log.Info($"[CARD_ACTION] Processing action: {action}"); + switch (action) + { + case "submit_basic": + var notifyValue = GetFormValue("notify") ?? "false"; + await context.Send($"Basic card submitted! Notify setting: {notifyValue}"); + break; + + case "submit_feedback": + var feedbackText = GetFormValue("feedback") ?? "No feedback provided"; + await context.Send($"Feedback received: {feedbackText}"); + break; + + case "create_task": + var title = GetFormValue("title") ?? "Untitled"; + var priority = GetFormValue("priority") ?? "medium"; + var dueDate = GetFormValue("due_date") ?? "No date"; + await context.Send($"Task created!\nTitle: {title}\nPriority: {priority}\nDue: {dueDate}"); + break; + + case "save_profile": + var name = GetFormValue("name") ?? "Unknown"; + var email = GetFormValue("email") ?? "No email"; + var subscribe = GetFormValue("subscribe") ?? "false"; + var age = GetFormValue("age"); + var location = GetFormValue("location") ?? "Not specified"; + + var response = $"Profile saved!\nName: {name}\nEmail: {email}\nSubscribed: {subscribe}"; + if (!string.IsNullOrEmpty(age)) + response += $"\nAge: {age}"; + if (location != "Not specified") + response += $"\nLocation: {location}"; + + await context.Send(response); + break; + + case "test_json": + await context.Send("JSON deserialization test successful!"); + break; + + default: + context.Log.Error($"[CARD_ACTION] Unknown action: {action}"); + return new ActionResponse.Message("Unknown action") { StatusCode = 400 }; + } - // Helper method to extract form field values (they're at root level, not in Value) - string? GetFormValue(string key) - { - if (data.TryGetValue(key, out var val)) - { - if (val is System.Text.Json.JsonElement element) - return element.GetString(); - return val?.ToString(); - } - return null; - } + return new ActionResponse.Message("Action processed successfully") { StatusCode = 200 }; +}); + +app.Run(); + +static string SanitizeForLog(string? input) +{ + if (input == null) return ""; + return input.Replace("\r", "").Replace("\n", ""); +} - switch (action) +static Microsoft.Teams.Cards.AdaptiveCard CreateBasicAdaptiveCard() +{ + return new Microsoft.Teams.Cards.AdaptiveCard + { + Schema = "http://adaptivecards.io/schemas/adaptive-card.json", + Body = new List + { + new TextBlock("Hello world") { Wrap = true, Weight = TextWeight.Bolder }, + new ToggleInput("Notify me") { Id = "notify" } + }, + Actions = new List + { + new ExecuteAction { - case "submit_basic": - var notifyValue = GetFormValue("notify") ?? "false"; - await client.Send($"Basic card submitted! Notify setting: {notifyValue}"); - break; - - case "submit_feedback": - var feedbackText = GetFormValue("feedback") ?? "No feedback provided"; - await client.Send($"Feedback received: {feedbackText}"); - break; - - case "create_task": - var title = GetFormValue("title") ?? "Untitled"; - var priority = GetFormValue("priority") ?? "medium"; - var dueDate = GetFormValue("due_date") ?? "No date"; - await client.Send($"Task created!\nTitle: {title}\nPriority: {priority}\nDue: {dueDate}"); - break; - - case "save_profile": - var name = GetFormValue("name") ?? "Unknown"; - var email = GetFormValue("email") ?? "No email"; - var subscribe = GetFormValue("subscribe") ?? "false"; - var age = GetFormValue("age"); - var location = GetFormValue("location") ?? "Not specified"; - - var response = $"Profile saved!\nName: {name}\nEmail: {email}\nSubscribed: {subscribe}"; - if (!string.IsNullOrEmpty(age)) - response += $"\nAge: {age}"; - if (location != "Not specified") - response += $"\nLocation: {location}"; - - await client.Send(response); - break; - - case "test_json": - await client.Send("✅ JSON deserialization test successful! The card was properly created from JSON and the action was processed correctly."); - break; - - default: - log.Error($"[CARD_ACTION] Unknown action: {action}"); - return new ActionResponse.Message("Unknown action") { StatusCode = 400 }; + Title = "Submit", + Data = new Union(new SubmitActionData { NonSchemaProperties = new Dictionary { { "action", "submit_basic" } } }), + AssociatedInputs = AssociatedInputs.Auto } - - return new ActionResponse.Message("Action processed successfully") { StatusCode = 200 }; } + }; +} - private static AdaptiveCard CreateBasicAdaptiveCard() +static Microsoft.Teams.Cards.AdaptiveCard CreateProfileCard() +{ + return new Microsoft.Teams.Cards.AdaptiveCard + { + Schema = "http://adaptivecards.io/schemas/adaptive-card.json", + Body = new List + { + new TextBlock("User Profile") { Weight = TextWeight.Bolder, Size = TextSize.Large }, + new TextInput { Id = "name", Label = "Name", Value = "John Doe" }, + new TextInput { Id = "email", Label = "Email", Value = "john@contoso.com" }, + new ToggleInput("Subscribe to newsletter") { Id = "subscribe", Value = "false" } + }, + Actions = new List { - return new AdaptiveCard + new ExecuteAction { - Schema = "http://adaptivecards.io/schemas/adaptive-card.json", - Body = new List - { - new TextBlock("Hello world") - { - Wrap = true, - Weight = TextWeight.Bolder - }, - new ToggleInput("Notify me") - { - Id = "notify" - } - }, - Actions = new List - { - new ExecuteAction - { - Title = "Submit", - Data = new Union(new SubmitActionData { NonSchemaProperties = new Dictionary { { "action", "submit_basic" } } }), - AssociatedInputs = AssociatedInputs.Auto - } - } - }; + Title = "Save", + Data = new Union(new SubmitActionData { NonSchemaProperties = new Dictionary { { "action", "save_profile" }, { "entity_id", "12345" } } }), + AssociatedInputs = AssociatedInputs.Auto + }, + new OpenUrlAction("https://adaptivecards.microsoft.com") { Title = "Learn More" } } + }; +} - private static AdaptiveCard CreateProfileCard() +static Microsoft.Teams.Cards.AdaptiveCard CreateProfileCardWithValidation() +{ + return new Microsoft.Teams.Cards.AdaptiveCard + { + Schema = "http://adaptivecards.io/schemas/adaptive-card.json", + Body = new List + { + new TextBlock("Profile with Validation") { Weight = TextWeight.Bolder, Size = TextSize.Large }, + new NumberInput { Id = "age", Label = "Age", IsRequired = true, Min = 0, Max = 120 }, + new TextInput { Id = "name", Label = "Name", IsRequired = true, ErrorMessage = "Name is required" }, + new TextInput { Id = "location", Label = "Location" } + }, + Actions = new List { - return new AdaptiveCard + new ExecuteAction { - Schema = "http://adaptivecards.io/schemas/adaptive-card.json", - Body = new List - { - new TextBlock("User Profile") - { - Weight = TextWeight.Bolder, - Size = TextSize.Large - }, - new TextInput - { - Id = "name", - Label = "Name", - Value = "John Doe" - }, - new TextInput - { - Id = "email", - Label = "Email", - Value = "john@contoso.com" - }, - new ToggleInput("Subscribe to newsletter") - { - Id = "subscribe", - Value = "false" - } - }, - Actions = new List - { - new ExecuteAction - { - Title = "Save", - Data = new Union(new SubmitActionData { NonSchemaProperties = new Dictionary { { "action", "save_profile" }, { "entity_id", "12345" } } }), - AssociatedInputs = AssociatedInputs.Auto - }, - new OpenUrlAction("https://adaptivecards.microsoft.com") - { - Title = "Learn More" - } - } - }; + Title = "Save", + Data = new Union(new SubmitActionData { NonSchemaProperties = new Dictionary { { "action", "save_profile" } } }), + AssociatedInputs = AssociatedInputs.Auto + } } + }; +} - private static AdaptiveCard CreateProfileCardWithValidation() +static Microsoft.Teams.Cards.AdaptiveCard CreateFeedbackCard() +{ + return new Microsoft.Teams.Cards.AdaptiveCard + { + Schema = "http://adaptivecards.io/schemas/adaptive-card.json", + Body = new List + { + new TextBlock("Feedback Form") { Weight = TextWeight.Bolder, Size = TextSize.Large }, + new TextInput { Id = "feedback", Label = "Your Feedback", Placeholder = "Please share your thoughts...", IsMultiline = true, IsRequired = true } + }, + Actions = new List { - return new AdaptiveCard + new ExecuteAction { - Schema = "http://adaptivecards.io/schemas/adaptive-card.json", - Body = new List - { - new TextBlock("Profile with Validation") - { - Weight = TextWeight.Bolder, - Size = TextSize.Large - }, - new NumberInput - { - Id = "age", - Label = "Age", - IsRequired = true, - Min = 0, - Max = 120 - }, - new TextInput - { - Id = "name", - Label = "Name", - IsRequired = true, - ErrorMessage = "Name is required" - }, - new TextInput - { - Id = "location", - Label = "Location" - } - }, - Actions = new List - { - new ExecuteAction - { - Title = "Save", - Data = new Union(new SubmitActionData { NonSchemaProperties = new Dictionary { { "action", "save_profile" } } }), - AssociatedInputs = AssociatedInputs.Auto - } - } - }; + Title = "Submit Feedback", + Data = new Union(new SubmitActionData { NonSchemaProperties = new Dictionary { { "action", "submit_feedback" } } }), + AssociatedInputs = AssociatedInputs.Auto + } } + }; +} - private static AdaptiveCard CreateFeedbackCard() +static Microsoft.Teams.Cards.AdaptiveCard CreateTaskFormCard() +{ + return new Microsoft.Teams.Cards.AdaptiveCard + { + Schema = "http://adaptivecards.io/schemas/adaptive-card.json", + Body = new List { - return new AdaptiveCard + new TextBlock("Create New Task") { Weight = TextWeight.Bolder, Size = TextSize.Large }, + new TextInput { Id = "title", Label = "Task Title", Placeholder = "Enter task title" }, + new TextInput { Id = "description", Label = "Description", Placeholder = "Enter task details", IsMultiline = true }, + new ChoiceSetInput { - Schema = "http://adaptivecards.io/schemas/adaptive-card.json", - Body = new List - { - new TextBlock("Feedback Form") - { - Weight = TextWeight.Bolder, - Size = TextSize.Large - }, - new TextInput - { - Id = "feedback", - Label = "Your Feedback", - Placeholder = "Please share your thoughts...", - IsMultiline = true, - IsRequired = true - } - }, - Actions = new List + Id = "priority", + Label = "Priority", + Value = "medium", + Choices = new List { - new ExecuteAction - { - Title = "Submit Feedback", - Data = new Union(new SubmitActionData { NonSchemaProperties = new Dictionary { { "action", "submit_feedback" } } }), - AssociatedInputs = AssociatedInputs.Auto - } + new() { Title = "High", Value = "high" }, + new() { Title = "Medium", Value = "medium" }, + new() { Title = "Low", Value = "low" } } - }; + }, + new DateInput { Id = "due_date", Label = "Due Date", Value = DateTime.Now.ToString("yyyy-MM-dd") } + }, + Actions = new List + { + new ExecuteAction + { + Title = "Create Task", + Data = new Union(new SubmitActionData { NonSchemaProperties = new Dictionary { { "action", "create_task" } } }), + AssociatedInputs = AssociatedInputs.Auto, + Style = ActionStyle.Positive + } } + }; +} - private static AdaptiveCard CreateTaskFormCard() - { - return new AdaptiveCard +static Microsoft.Teams.Cards.AdaptiveCard CreateCardFromJson() +{ + var cardJson = @"{ + ""type"": ""AdaptiveCard"", + ""body"": [ { - Schema = "http://adaptivecards.io/schemas/adaptive-card.json", - Body = new List - { - new TextBlock("Create New Task") - { - Weight = TextWeight.Bolder, - Size = TextSize.Large - }, - new TextInput - { - Id = "title", - Label = "Task Title", - Placeholder = "Enter task title" - }, - new TextInput + ""type"": ""ColumnSet"", + ""columns"": [ { - Id = "description", - Label = "Description", - Placeholder = "Enter task details", - IsMultiline = true + ""type"": ""Column"", + ""verticalContentAlignment"": ""center"", + ""items"": [{ ""type"": ""Image"", ""style"": ""Person"", ""url"": ""https://aka.ms/AAp9xo4"", ""size"": ""Small"", ""altText"": ""Portrait of David Claux"" }], + ""width"": ""auto"" }, - new ChoiceSetInput { - Id = "priority", - Label = "Priority", - Value = "medium", - Choices = new List - { - new() { Title = "High", Value = "high" }, - new() { Title = "Medium", Value = "medium" }, - new() { Title = "Low", Value = "low" } - } + ""type"": ""Column"", + ""spacing"": ""medium"", + ""verticalContentAlignment"": ""center"", + ""items"": [{ ""type"": ""TextBlock"", ""weight"": ""Bolder"", ""text"": ""David Claux"", ""wrap"": true }], + ""width"": ""auto"" }, - new DateInput { - Id = "due_date", - Label = "Due Date", - Value = DateTime.Now.ToString("yyyy-MM-dd") + ""type"": ""Column"", + ""spacing"": ""medium"", + ""verticalContentAlignment"": ""center"", + ""items"": [{ ""type"": ""TextBlock"", ""text"": ""Principal Platform Architect at Microsoft"", ""isSubtle"": true, ""wrap"": true }], + ""width"": ""stretch"" } - }, - Actions = new List - { - new ExecuteAction - { - Title = "Create Task", - Data = new Union(new SubmitActionData { NonSchemaProperties = new Dictionary { { "action", "create_task" } } }), - AssociatedInputs = AssociatedInputs.Auto, - Style = ActionStyle.Positive - } - } - }; - } - - private static AdaptiveCard CreateCardFromJson() + ] + }, + { ""type"": ""TextBlock"", ""text"": ""This card was created from JSON deserialization!"", ""wrap"": true, ""color"": ""good"", ""spacing"": ""medium"" } + ], + ""actions"": [{ ""type"": ""Action.Execute"", ""title"": ""Test JSON Action"", ""data"": { ""Value"": { ""action"": ""test_json"" } }, ""associatedInputs"": ""auto"" }], + ""version"": ""1.5"", + ""schema"": ""http://adaptivecards.io/schemas/adaptive-card.json"" + }"; + + try + { + var card = JsonSerializer.Deserialize(cardJson); + return card ?? throw new InvalidOperationException("Failed to deserialize card"); + } + catch (Exception ex) + { + Console.WriteLine($"Error deserializing card JSON: {ex.Message}"); + return new Microsoft.Teams.Cards.AdaptiveCard { - // JSON similar to the Python create_model_validate_card example - var cardJson = @"{ - ""type"": ""AdaptiveCard"", - ""body"": [ - { - ""type"": ""ColumnSet"", - ""columns"": [ - { - ""type"": ""Column"", - ""verticalContentAlignment"": ""center"", - ""items"": [ - { - ""type"": ""Image"", - ""style"": ""Person"", - ""url"": ""https://aka.ms/AAp9xo4"", - ""size"": ""Small"", - ""altText"": ""Portrait of David Claux"" - } - ], - ""width"": ""auto"" - }, - { - ""type"": ""Column"", - ""spacing"": ""medium"", - ""verticalContentAlignment"": ""center"", - ""items"": [ - { - ""type"": ""TextBlock"", - ""weight"": ""Bolder"", - ""text"": ""David Claux"", - ""wrap"": true - } - ], - ""width"": ""auto"" - }, - { - ""type"": ""Column"", - ""spacing"": ""medium"", - ""verticalContentAlignment"": ""center"", - ""items"": [ - { - ""type"": ""TextBlock"", - ""text"": ""Principal Platform Architect at Microsoft"", - ""isSubtle"": true, - ""wrap"": true - } - ], - ""width"": ""stretch"" - } - ] - }, - { - ""type"": ""TextBlock"", - ""text"": ""This card was created from JSON deserialization!"", - ""wrap"": true, - ""color"": ""good"", - ""spacing"": ""medium"" - } - ], - ""actions"": [ - { - ""type"": ""Action.Execute"", - ""title"": ""Test JSON Action"", - ""data"": { - ""Value"": { - ""action"": ""test_json"" - } - }, - ""associatedInputs"": ""auto"" - } - ], - ""version"": ""1.5"", - ""schema"": ""http://adaptivecards.io/schemas/adaptive-card.json"" - }"; - - try - { - var card = JsonSerializer.Deserialize(cardJson); - - return card ?? throw new InvalidOperationException("Failed to deserialize card"); - } - catch (Exception ex) + Schema = "http://adaptivecards.io/schemas/adaptive-card.json", + Body = new List { - Console.WriteLine($"Error deserializing card JSON: {ex.Message}"); - // If deserialization fails, return a fallback card with error info - return new AdaptiveCard - { - Schema = "http://adaptivecards.io/schemas/adaptive-card.json", - Body = new List - { - new TextBlock("JSON Deserialization Test") - { - Weight = TextWeight.Bolder, - Size = TextSize.Large, - Color = TextColor.Attention - }, - new TextBlock($"Deserialization failed: {ex.Message}") - { - Wrap = true, - Color = TextColor.Attention - } - } - }; + new TextBlock("JSON Deserialization Test") { Weight = TextWeight.Bolder, Size = TextSize.Large, Color = TextColor.Attention }, + new TextBlock($"Deserialization failed: {ex.Message}") { Wrap = true, Color = TextColor.Attention } } - } - - [Microsoft.Teams.Apps.Events.Event("activity")] - public void OnEvent(IPlugin plugin, Microsoft.Teams.Apps.Events.Event @event) - { - Console.WriteLine("!!HIT!!"); - } - - // Helper method to sanitize user input for logging - private static string SanitizeForLog(string input) - { - if (input == null) return ""; - // Remove carriage returns and line feeds to prevent log forging - return input.Replace("\r", "").Replace("\n", ""); - } + }; } -} \ No newline at end of file +} diff --git a/Samples/Samples.Dialogs/Program.cs b/Samples/Samples.Dialogs/Program.cs index cb1152f8..d6ea3aa0 100644 --- a/Samples/Samples.Dialogs/Program.cs +++ b/Samples/Samples.Dialogs/Program.cs @@ -1,385 +1,313 @@ using System.Text.Json; using System.Text.Json.Serialization; -using Microsoft.Teams.Api.Activities.Invokes; -using Microsoft.Teams.Apps; using Microsoft.Teams.Apps.Activities; using Microsoft.Teams.Apps.Activities.Invokes; -using Microsoft.Teams.Apps.Annotations; using Microsoft.Teams.Apps.Extensions; using Microsoft.Teams.Common; using Microsoft.Teams.Plugins.AspNetCore.DevTools.Extensions; using Microsoft.Teams.Plugins.AspNetCore.Extensions; -namespace Samples.Dialogs; +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddOpenApi(); +builder.AddTeams().AddTeamsDevTools(); -public static partial class Program -{ - public static void Main(string[] args) - { - var builder = WebApplication.CreateBuilder(args); - builder.Services.AddOpenApi(); - builder.Services.AddTransient(); - builder.AddTeams().AddTeamsDevTools(); - - var app = builder.Build(); +var app = builder.Build(); - if (app.Environment.IsDevelopment()) - { - app.MapOpenApi(); - } - - app.UseHttpsRedirection(); - app.UseTeams(); - app.AddTab("dialog-form", "Web/dialog-form"); - app.Run(); - } - - [TeamsController] - public class Controller - { - private readonly IConfiguration _configuration; - - public Controller(IConfiguration configuration) - { - _configuration = configuration; - } - - [Message] - public async Task OnMessage([Context] Microsoft.Teams.Api.Activities.MessageActivity activity, [Context] IContext.Client client, [Context] Microsoft.Teams.Common.Logging.ILogger log) - { - log.Info($"[MESSAGE] Received: {SanitizeForLog(activity.Text)}"); - log.Info($"[MESSAGE] From: {SanitizeForLog(activity.From?.Name ?? "unknown")}"); - - // Create the launcher adaptive card - var card = CreateDialogLauncherCard(); - await client.Send(card); - } +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); +} - [TaskFetch] - public Microsoft.Teams.Api.TaskModules.Response OnTaskFetch([Context] Tasks.FetchActivity activity, [Context] IContext.Client client, [Context] Microsoft.Teams.Common.Logging.ILogger log) - { - log.Info("[TASK_FETCH] Task fetch request received"); +app.UseHttpsRedirection(); +var teams = app.UseTeams(); +app.AddTab("dialog-form", "Web/dialog-form"); - var data = activity.Value?.Data as JsonElement?; - if (data == null) - { - log.Info("[TASK_FETCH] No data found in the activity value"); - return new Microsoft.Teams.Api.TaskModules.Response(new Microsoft.Teams.Api.TaskModules.MessageTask("No data found in the activity value")); - } +teams.OnMessage(async context => +{ + var activity = context.Activity; + context.Log.Info($"[MESSAGE] Received: {SanitizeForLog(activity.Text)}"); + context.Log.Info($"[MESSAGE] From: {SanitizeForLog(activity.From?.Name ?? "unknown")}"); - var dialogType = data.Value.TryGetProperty("opendialogtype", out var dialogTypeElement) && dialogTypeElement.ValueKind == JsonValueKind.String - ? dialogTypeElement.GetString() - : null; + var card = CreateDialogLauncherCard(); + await context.Send(card); +}); - log.Info($"[TASK_FETCH] Dialog type: {dialogType}"); +teams.OnTaskFetch(context => +{ + var activity = context.Activity; + context.Log.Info("[TASK_FETCH] Task fetch request received"); - return dialogType switch - { - "simple_form" => CreateSimpleFormDialog(), - "webpage_dialog" => CreateWebpageDialog(_configuration, log), - "multi_step_form" => CreateMultiStepFormDialog(), - "mixed_example" => CreateMixedExampleDialog(), - _ => new Microsoft.Teams.Api.TaskModules.Response(new Microsoft.Teams.Api.TaskModules.MessageTask("Unknown dialog type")) - }; - } + var data = activity.Value?.Data as JsonElement?; + if (data == null) + { + context.Log.Info("[TASK_FETCH] No data found in the activity value"); + return Task.FromResult(new Microsoft.Teams.Api.TaskModules.Response(new Microsoft.Teams.Api.TaskModules.MessageTask("No data found in the activity value"))); + } - [TaskSubmit] - public async Task OnTaskSubmit([Context] Tasks.SubmitActivity activity, [Context] IContext.Client client, [Context] Microsoft.Teams.Common.Logging.ILogger log) - { - log.Info("[TASK_SUBMIT] Task submit request received"); + var dialogType = data.Value.TryGetProperty("opendialogtype", out var dialogTypeElement) && dialogTypeElement.ValueKind == JsonValueKind.String + ? dialogTypeElement.GetString() + : null; - var data = activity.Value?.Data as JsonElement?; - if (data == null) - { - log.Info("[TASK_SUBMIT] No data found in the activity value"); - return new Microsoft.Teams.Api.TaskModules.Response(new Microsoft.Teams.Api.TaskModules.MessageTask("No data found in the activity value")); - } + context.Log.Info($"[TASK_FETCH] Dialog type: {dialogType}"); - var submissionType = data.Value.TryGetProperty("submissiondialogtype", out var submissionTypeObj) && submissionTypeObj.ValueKind == JsonValueKind.String - ? submissionTypeObj.ToString() - : null; + var response = dialogType switch + { + "simple_form" => CreateSimpleFormDialog(), + "webpage_dialog" => CreateWebpageDialog(app.Configuration, context.Log), + "multi_step_form" => CreateMultiStepFormDialog(), + "mixed_example" => CreateMixedExampleDialog(), + _ => new Microsoft.Teams.Api.TaskModules.Response(new Microsoft.Teams.Api.TaskModules.MessageTask("Unknown dialog type")) + }; + return Task.FromResult(response); +}); + +teams.OnTaskSubmit(async context => +{ + var activity = context.Activity; + context.Log.Info("[TASK_SUBMIT] Task submit request received"); - log.Info($"[TASK_SUBMIT] Submission type: {submissionType}"); + var data = activity.Value?.Data as JsonElement?; + if (data == null) + { + context.Log.Info("[TASK_SUBMIT] No data found in the activity value"); + return new Microsoft.Teams.Api.TaskModules.Response(new Microsoft.Teams.Api.TaskModules.MessageTask("No data found in the activity value")); + } - string? GetFormValue(string key) - { - if (data.Value.TryGetProperty(key, out var val)) - { - if (val is System.Text.Json.JsonElement element) - return element.GetString(); - return val.ToString(); - } - return null; - } + var submissionType = data.Value.TryGetProperty("submissiondialogtype", out var submissionTypeObj) && submissionTypeObj.ValueKind == JsonValueKind.String + ? submissionTypeObj.ToString() + : null; - switch (submissionType) - { - case "simple_form": - var name = GetFormValue("name") ?? "Unknown"; - await client.Send($"Hi {name}, thanks for submitting the form!"); - return new Microsoft.Teams.Api.TaskModules.Response(new Microsoft.Teams.Api.TaskModules.MessageTask("Form was submitted")); - - case "webpage_dialog": - var webName = GetFormValue("name") ?? "Unknown"; - var email = GetFormValue("email") ?? "No email"; - await client.Send($"Hi {webName}, thanks for submitting the form! We got that your email is {email}"); - return new Microsoft.Teams.Api.TaskModules.Response(new Microsoft.Teams.Api.TaskModules.MessageTask("Form submitted successfully")); - - case "webpage_dialog_step_1": - var nameStep1 = GetFormValue("name") ?? "Unknown"; - var nextStepCardJson = $$""" - { - "type": "AdaptiveCard", - "version": "1.4", - "body": [ - { - "type": "TextBlock", - "text": "Email", - "size": "Large", - "weight": "Bolder" - }, - { - "type": "Input.Text", - "id": "email", - "label": "Email", - "placeholder": "Enter your email", - "isRequired": true - } - ], - "actions": [ - { - "type": "Action.Submit", - "title": "Submit", - "data": {"submissiondialogtype": "webpage_dialog_step_2", "name": "{{nameStep1}}"} - } - ] - } - """; - - var nextStepCard = JsonSerializer.Deserialize(nextStepCardJson) - ?? throw new InvalidOperationException("Failed to deserialize next step card"); - - var nextStepTaskInfo = new Microsoft.Teams.Api.TaskModules.TaskInfo - { - Title = $"Thanks {nameStep1} - Get Email", - Card = new Microsoft.Teams.Api.Attachment - { - ContentType = new Microsoft.Teams.Api.ContentType("application/vnd.microsoft.card.adaptive"), - Content = nextStepCard - } - }; - - return new Microsoft.Teams.Api.TaskModules.Response(new Microsoft.Teams.Api.TaskModules.ContinueTask(nextStepTaskInfo)); - - case "webpage_dialog_step_2": - var nameStep2 = GetFormValue("name") ?? "Unknown"; - var emailStep2 = GetFormValue("email") ?? "No email"; - await client.Send($"Hi {nameStep2}, thanks for submitting the form! We got that your email is {emailStep2}"); - return new Microsoft.Teams.Api.TaskModules.Response(new Microsoft.Teams.Api.TaskModules.MessageTask("Multi-step form completed successfully")); - - default: - return new Microsoft.Teams.Api.TaskModules.Response(new Microsoft.Teams.Api.TaskModules.MessageTask("Unknown submission type")); - } - } + context.Log.Info($"[TASK_SUBMIT] Submission type: {submissionType}"); - private static Microsoft.Teams.Cards.AdaptiveCard CreateDialogLauncherCard() + string? GetFormValue(string key) + { + if (data.Value.TryGetProperty(key, out var val)) { - var card = new Microsoft.Teams.Cards.AdaptiveCard - { - Body = new List - { - new Microsoft.Teams.Cards.TextBlock("Select the examples you want to see!") - { - Size = Microsoft.Teams.Cards.TextSize.Large, - Weight = Microsoft.Teams.Cards.TextWeight.Bolder - } - }, - Actions = new List - { - new Microsoft.Teams.Cards.TaskFetchAction( - Microsoft.Teams.Cards.TaskFetchAction.FromObject(new { opendialogtype = "simple_form" })) -{ - Title = "Simple form test" -}, -new Microsoft.Teams.Cards.TaskFetchAction( - Microsoft.Teams.Cards.TaskFetchAction.FromObject(new { opendialogtype = "webpage_dialog" })) -{ - Title = "Webpage Dialog" -}, -new Microsoft.Teams.Cards.TaskFetchAction( - Microsoft.Teams.Cards.TaskFetchAction.FromObject(new { opendialogtype = "multi_step_form" })) -{ - Title = "Multi-step Form" -}, -new Microsoft.Teams.Cards.TaskFetchAction( - Microsoft.Teams.Cards.TaskFetchAction.FromObject(new { opendialogtype = "mixed_example" })) -{ - Title = "Mixed Example" -} - } - }; - - var serializedCard = JsonSerializer.Serialize(card, new JsonSerializerOptions { WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }); - Console.WriteLine($"[DEBUG] Launcher Card JSON: {serializedCard}"); - - return card; + if (val is JsonElement element) + return element.GetString(); + return val.ToString(); } + return null; + } - private static Microsoft.Teams.Api.TaskModules.Response CreateSimpleFormDialog() - { - // Create card from JSON similar to Python's model_validate approach - var cardJson = """ + switch (submissionType) + { + case "simple_form": + var name = GetFormValue("name") ?? "Unknown"; + await context.Send($"Hi {name}, thanks for submitting the form!"); + return new Microsoft.Teams.Api.TaskModules.Response(new Microsoft.Teams.Api.TaskModules.MessageTask("Form was submitted")); + + case "webpage_dialog": + var webName = GetFormValue("name") ?? "Unknown"; + var email = GetFormValue("email") ?? "No email"; + await context.Send($"Hi {webName}, thanks for submitting the form! We got that your email is {email}"); + return new Microsoft.Teams.Api.TaskModules.Response(new Microsoft.Teams.Api.TaskModules.MessageTask("Form submitted successfully")); + + case "webpage_dialog_step_1": + var nameStep1 = GetFormValue("name") ?? "Unknown"; + var nextStepCardJson = $$""" { "type": "AdaptiveCard", "version": "1.4", "body": [ - { - "type": "TextBlock", - "text": "This is a simple form", - "size": "Large", - "weight": "Bolder" - }, - { - "type": "Input.Text", - "id": "name", - "label": "Name", - "placeholder": "Enter your name", - "isRequired": true - } + { "type": "TextBlock", "text": "Email", "size": "Large", "weight": "Bolder" }, + { "type": "Input.Text", "id": "email", "label": "Email", "placeholder": "Enter your email", "isRequired": true } ], - "actions": [ - {"type": "Action.Submit", "title": "Submit", "data": {"submissiondialogtype": "simple_form"}} - ] + "actions": [{ "type": "Action.Submit", "title": "Submit", "data": {"submissiondialogtype": "webpage_dialog_step_2", "name": "{{nameStep1}}"} }] } """; - var dialogCard = JsonSerializer.Deserialize(cardJson) - ?? throw new InvalidOperationException("Failed to deserialize simple form card"); - - var serializedCard = JsonSerializer.Serialize(dialogCard); - Console.WriteLine($"[DEBUG] Simple Form Card JSON: {serializedCard}"); + var nextStepCard = JsonSerializer.Deserialize(nextStepCardJson) + ?? throw new InvalidOperationException("Failed to deserialize next step card"); - var taskInfo = new Microsoft.Teams.Api.TaskModules.TaskInfo + var nextStepTaskInfo = new Microsoft.Teams.Api.TaskModules.TaskInfo { - Title = "Simple Form Dialog", + Title = $"Thanks {nameStep1} - Get Email", Card = new Microsoft.Teams.Api.Attachment { ContentType = new Microsoft.Teams.Api.ContentType("application/vnd.microsoft.card.adaptive"), - Content = dialogCard + Content = nextStepCard } }; + return new Microsoft.Teams.Api.TaskModules.Response(new Microsoft.Teams.Api.TaskModules.ContinueTask(nextStepTaskInfo)); - var continueTask = new Microsoft.Teams.Api.TaskModules.ContinueTask(taskInfo); + case "webpage_dialog_step_2": + var nameStep2 = GetFormValue("name") ?? "Unknown"; + var emailStep2 = GetFormValue("email") ?? "No email"; + await context.Send($"Hi {nameStep2}, thanks for submitting the form! We got that your email is {emailStep2}"); + return new Microsoft.Teams.Api.TaskModules.Response(new Microsoft.Teams.Api.TaskModules.MessageTask("Multi-step form completed successfully")); - // Debug the ContinueTask before wrapping in Response - Console.WriteLine($"[DEBUG] continueTask.Value is null: {continueTask.Value == null}"); - Console.WriteLine($"[DEBUG] continueTask.Value.Title: '{continueTask.Value?.Title}'"); - Console.WriteLine($"[DEBUG] continueTask.Value.Card is null: {continueTask.Value?.Card == null}"); - - var debugOptions = new JsonSerializerOptions - { - DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.Never, - WriteIndented = true - }; - var continueTaskJson = JsonSerializer.Serialize(continueTask, debugOptions); - Console.WriteLine($"[DEBUG] ContinueTask JSON (no ignore): {continueTaskJson}"); + default: + return new Microsoft.Teams.Api.TaskModules.Response(new Microsoft.Teams.Api.TaskModules.MessageTask("Unknown submission type")); + } +}); - var response = new Microsoft.Teams.Api.TaskModules.Response(continueTask); - var serializedResponse = JsonSerializer.Serialize(response, debugOptions); - Console.WriteLine($"[DEBUG] Response JSON (no ignore): {serializedResponse}"); +app.Run(); - return response; - } +static string SanitizeForLog(string? input) +{ + if (input == null) return ""; + return input.Replace("\r", "").Replace("\n", ""); +} - private static Microsoft.Teams.Api.TaskModules.Response CreateWebpageDialog(IConfiguration configuration, Microsoft.Teams.Common.Logging.ILogger log) +static Microsoft.Teams.Cards.AdaptiveCard CreateDialogLauncherCard() +{ + var card = new Microsoft.Teams.Cards.AdaptiveCard + { + Body = new List { - var botEndpoint = configuration["BotEndpoint"]; - if (string.IsNullOrEmpty(botEndpoint)) + new Microsoft.Teams.Cards.TextBlock("Select the examples you want to see!") { - log.Warn("No remote endpoint detected. Using webpages for dialog will not work as expected"); - botEndpoint = "http://localhost:3978"; // Fallback for local development + Size = Microsoft.Teams.Cards.TextSize.Large, + Weight = Microsoft.Teams.Cards.TextWeight.Bolder } - else + }, + Actions = new List + { + new Microsoft.Teams.Cards.TaskFetchAction( + Microsoft.Teams.Cards.TaskFetchAction.FromObject(new { opendialogtype = "simple_form" })) { - log.Info($"Using BotEndpoint: {botEndpoint}/tabs/dialog-form"); + Title = "Simple form test" + }, + new Microsoft.Teams.Cards.TaskFetchAction( + Microsoft.Teams.Cards.TaskFetchAction.FromObject(new { opendialogtype = "webpage_dialog" })) + { + Title = "Webpage Dialog" + }, + new Microsoft.Teams.Cards.TaskFetchAction( + Microsoft.Teams.Cards.TaskFetchAction.FromObject(new { opendialogtype = "multi_step_form" })) + { + Title = "Multi-step Form" + }, + new Microsoft.Teams.Cards.TaskFetchAction( + Microsoft.Teams.Cards.TaskFetchAction.FromObject(new { opendialogtype = "mixed_example" })) + { + Title = "Mixed Example" } + } + }; - var taskInfo = new Microsoft.Teams.Api.TaskModules.TaskInfo - { - Title = "Webpage Dialog", - Width = new Union(1000), - Height = new Union(800), - Url = $"{botEndpoint}/tabs/dialog-form" - }; + var serializedCard = JsonSerializer.Serialize(card, new JsonSerializerOptions { WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }); + Console.WriteLine($"[DEBUG] Launcher Card JSON: {serializedCard}"); - return new Microsoft.Teams.Api.TaskModules.Response(new Microsoft.Teams.Api.TaskModules.ContinueTask(taskInfo)); - } + return card; +} - private static Microsoft.Teams.Api.TaskModules.Response CreateMultiStepFormDialog() - { - var cardJson = """ - { - "type": "AdaptiveCard", - "version": "1.4", - "body": [ - { - "type": "TextBlock", - "text": "This is a multi-step form", - "size": "Large", - "weight": "Bolder" - }, - { - "type": "Input.Text", - "id": "name", - "label": "Name", - "placeholder": "Enter your name", - "isRequired": true - } - ], - "actions": [ - { - "type": "Action.Submit", - "title": "Submit", - "data": {"submissiondialogtype": "webpage_dialog_step_1"} - } - ] - } - """; +static Microsoft.Teams.Api.TaskModules.Response CreateSimpleFormDialog() +{ + var cardJson = """ + { + "type": "AdaptiveCard", + "version": "1.4", + "body": [ + { "type": "TextBlock", "text": "This is a simple form", "size": "Large", "weight": "Bolder" }, + { "type": "Input.Text", "id": "name", "label": "Name", "placeholder": "Enter your name", "isRequired": true } + ], + "actions": [{"type": "Action.Submit", "title": "Submit", "data": {"submissiondialogtype": "simple_form"}}] + } + """; - var dialogCard = JsonSerializer.Deserialize(cardJson) - ?? throw new InvalidOperationException("Failed to deserialize multi-step form card"); + var dialogCard = JsonSerializer.Deserialize(cardJson) + ?? throw new InvalidOperationException("Failed to deserialize simple form card"); - var taskInfo = new Microsoft.Teams.Api.TaskModules.TaskInfo - { - Title = "Multi-step Form Dialog", - Card = new Microsoft.Teams.Api.Attachment - { - ContentType = new Microsoft.Teams.Api.ContentType("application/vnd.microsoft.card.adaptive"), - Content = dialogCard - } - }; + var serializedCard = JsonSerializer.Serialize(dialogCard); + Console.WriteLine($"[DEBUG] Simple Form Card JSON: {serializedCard}"); - return new Microsoft.Teams.Api.TaskModules.Response(new Microsoft.Teams.Api.TaskModules.ContinueTask(taskInfo)); + var taskInfo = new Microsoft.Teams.Api.TaskModules.TaskInfo + { + Title = "Simple Form Dialog", + Card = new Microsoft.Teams.Api.Attachment + { + ContentType = new Microsoft.Teams.Api.ContentType("application/vnd.microsoft.card.adaptive"), + Content = dialogCard } + }; - private static Microsoft.Teams.Api.TaskModules.Response CreateMixedExampleDialog() - { - var taskInfo = new Microsoft.Teams.Api.TaskModules.TaskInfo - { - Title = "Mixed Example (C# Sample)", - Width = new Union(800), - Height = new Union(600), - Url = "https://teams.microsoft.com/l/task/example-mixed" - }; + var continueTask = new Microsoft.Teams.Api.TaskModules.ContinueTask(taskInfo); - return new Microsoft.Teams.Api.TaskModules.Response(new Microsoft.Teams.Api.TaskModules.ContinueTask(taskInfo)); - } + Console.WriteLine($"[DEBUG] continueTask.Value is null: {continueTask.Value == null}"); + Console.WriteLine($"[DEBUG] continueTask.Value.Title: '{continueTask.Value?.Title}'"); + Console.WriteLine($"[DEBUG] continueTask.Value.Card is null: {continueTask.Value?.Card == null}"); + + var debugOptions = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.Never, + WriteIndented = true + }; + var continueTaskJson = JsonSerializer.Serialize(continueTask, debugOptions); + Console.WriteLine($"[DEBUG] ContinueTask JSON (no ignore): {continueTaskJson}"); + + var response = new Microsoft.Teams.Api.TaskModules.Response(continueTask); + var serializedResponse = JsonSerializer.Serialize(response, debugOptions); + Console.WriteLine($"[DEBUG] Response JSON (no ignore): {serializedResponse}"); + + return response; +} + +static Microsoft.Teams.Api.TaskModules.Response CreateWebpageDialog(IConfiguration configuration, Microsoft.Teams.Common.Logging.ILogger log) +{ + var botEndpoint = configuration["BotEndpoint"]; + if (string.IsNullOrEmpty(botEndpoint)) + { + log.Warn("No remote endpoint detected. Using webpages for dialog will not work as expected"); + botEndpoint = "http://localhost:3978"; + } + else + { + log.Info($"Using BotEndpoint: {botEndpoint}/tabs/dialog-form"); + } + + var taskInfo = new Microsoft.Teams.Api.TaskModules.TaskInfo + { + Title = "Webpage Dialog", + Width = new Union(1000), + Height = new Union(800), + Url = $"{botEndpoint}/tabs/dialog-form" + }; - private static string SanitizeForLog(string? input) + return new Microsoft.Teams.Api.TaskModules.Response(new Microsoft.Teams.Api.TaskModules.ContinueTask(taskInfo)); +} + +static Microsoft.Teams.Api.TaskModules.Response CreateMultiStepFormDialog() +{ + var cardJson = """ + { + "type": "AdaptiveCard", + "version": "1.4", + "body": [ + { "type": "TextBlock", "text": "This is a multi-step form", "size": "Large", "weight": "Bolder" }, + { "type": "Input.Text", "id": "name", "label": "Name", "placeholder": "Enter your name", "isRequired": true } + ], + "actions": [{ "type": "Action.Submit", "title": "Submit", "data": {"submissiondialogtype": "webpage_dialog_step_1"} }] + } + """; + + var dialogCard = JsonSerializer.Deserialize(cardJson) + ?? throw new InvalidOperationException("Failed to deserialize multi-step form card"); + + var taskInfo = new Microsoft.Teams.Api.TaskModules.TaskInfo + { + Title = "Multi-step Form Dialog", + Card = new Microsoft.Teams.Api.Attachment { - if (input == null) return ""; - return input.Replace("\r", "").Replace("\n", ""); + ContentType = new Microsoft.Teams.Api.ContentType("application/vnd.microsoft.card.adaptive"), + Content = dialogCard } - } -} \ No newline at end of file + }; + + return new Microsoft.Teams.Api.TaskModules.Response(new Microsoft.Teams.Api.TaskModules.ContinueTask(taskInfo)); +} + +static Microsoft.Teams.Api.TaskModules.Response CreateMixedExampleDialog() +{ + var taskInfo = new Microsoft.Teams.Api.TaskModules.TaskInfo + { + Title = "Mixed Example (C# Sample)", + Width = new Union(800), + Height = new Union(600), + Url = "https://teams.microsoft.com/l/task/example-mixed" + }; + + return new Microsoft.Teams.Api.TaskModules.Response(new Microsoft.Teams.Api.TaskModules.ContinueTask(taskInfo)); +} diff --git a/Samples/Samples.Echo/Program.cs b/Samples/Samples.Echo/Program.cs index 20cdd0d4..a55585e3 100644 --- a/Samples/Samples.Echo/Program.cs +++ b/Samples/Samples.Echo/Program.cs @@ -1,57 +1,23 @@ -using Microsoft.Teams.Api.Activities; -using Microsoft.Teams.Apps; using Microsoft.Teams.Apps.Activities; -using Microsoft.Teams.Apps.Annotations; using Microsoft.Teams.Apps.Extensions; -using Microsoft.Teams.Apps.Plugins; -using Microsoft.Teams.Plugins.AspNetCore.DevTools.Extensions; using Microsoft.Teams.Plugins.AspNetCore.Extensions; -namespace Samples.Echo; +var builder = WebApplication.CreateBuilder(args); +builder.AddTeams(); +var app = builder.Build(); +var teams = app.UseTeams(); -public static partial class Program +teams.OnActivity(async context => { - public static void Main(string[] args) - { - var builder = WebApplication.CreateBuilder(args); - builder.Services.AddOpenApi(); - builder.Services.AddTransient(); - builder.AddTeams().AddTeamsDevTools(); + context.Log.Info(context.AppId); + await context.Next(); +}); - var app = builder.Build(); - - if (app.Environment.IsDevelopment()) - { - app.MapOpenApi(); - } - - app.UseHttpsRedirection(); - app.UseTeams(); - app.Run(); - } - - [TeamsController] - public class Controller - { - [Activity] - public async Task OnActivity(IContext context, [Context] IContext.Next next) - { - context.Log.Info(context.AppId); - await next(); - } - - [Message] - public async Task OnMessage([Context] MessageActivity activity, [Context] IContext.Client client, [Context] Microsoft.Teams.Common.Logging.ILogger log) - { - log.Info("hit!"); - await client.Typing(); - await client.Send($"you said '{activity.Text}'"); - } +teams.OnMessage(async context => +{ + context.Log.Info("hit!"); + await context.Typing(); + await context.Send($"you said '{context.Activity.Text}'"); +}); - [Microsoft.Teams.Apps.Events.Event("activity")] - public void OnEvent(IPlugin plugin, Microsoft.Teams.Apps.Events.Event @event) - { - Console.WriteLine("!!HIT!!"); - } - } -} \ No newline at end of file +app.Run(); \ No newline at end of file diff --git a/Samples/Samples.Lights/Controller.cs b/Samples/Samples.Lights/Controller.cs deleted file mode 100644 index 7404906b..00000000 --- a/Samples/Samples.Lights/Controller.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Text.Json; - -using Microsoft.Teams.AI.Models.OpenAI; -using Microsoft.Teams.Api.Activities; -using Microsoft.Teams.Apps; -using Microsoft.Teams.Apps.Activities; -using Microsoft.Teams.Apps.Annotations; - -namespace Samples.Lights; - -[TeamsController] -public class Controller(Func _promptFactory) -{ - - [Message("/history")] - public async Task OnHistory(IContext context) - { - var state = State.From(context); - await context.Send(JsonSerializer.Serialize(state.Messages, new JsonSerializerOptions() - { - WriteIndented = true - })); - } - - [Message] - public async Task OnMessage(IContext context) - { - var state = State.From(context); - - var prompt = _promptFactory(); - await prompt.Send(context.Activity.Text, new() { Messages = state.Messages }, (chunk) => Task.Run(() => - { - context.Stream.Emit(chunk); - }), context.CancellationToken); - - state.Save(context); - } -} \ No newline at end of file diff --git a/Samples/Samples.Lights/Program.cs b/Samples/Samples.Lights/Program.cs index 370cd284..e7f5b42b 100644 --- a/Samples/Samples.Lights/Program.cs +++ b/Samples/Samples.Lights/Program.cs @@ -1,4 +1,8 @@ +using System.Text.Json; + +using Microsoft.Teams.AI.Models.OpenAI; using Microsoft.Teams.AI.Models.OpenAI.Extensions; +using Microsoft.Teams.Apps.Activities; using Microsoft.Teams.Apps.Extensions; using Microsoft.Teams.Plugins.AspNetCore.DevTools.Extensions; using Microsoft.Teams.Plugins.AspNetCore.Extensions; @@ -6,10 +10,34 @@ using Samples.Lights; var builder = WebApplication.CreateBuilder(args); -builder.Services.AddTransient(); builder.AddTeams().AddTeamsDevTools().AddOpenAI(); var app = builder.Build(); -app.UseTeams(); -app.Run(); \ No newline at end of file +// Get the prompt factory from services +var promptFactory = app.Services.GetRequiredService>(); + +var teams = app.UseTeams(); + +teams.OnMessage("/history", async context => +{ + var state = State.From(context); + await context.Send(JsonSerializer.Serialize(state.Messages, new JsonSerializerOptions() + { + WriteIndented = true + })); +}); + +teams.OnMessage(async context => +{ + var state = State.From(context); + var prompt = promptFactory(); + await prompt.Send(context.Activity.Text, new() { Messages = state.Messages }, (chunk) => Task.Run(() => + { + context.Stream.Emit(chunk); + }), context.CancellationToken); + + state.Save(context); +}); + +app.Run(); diff --git a/Samples/Samples.MessageExtensions/Program.cs b/Samples/Samples.MessageExtensions/Program.cs index 8eb38167..399f2228 100644 --- a/Samples/Samples.MessageExtensions/Program.cs +++ b/Samples/Samples.MessageExtensions/Program.cs @@ -1,536 +1,410 @@ using System.Text.Json; using Microsoft.Teams.Api.Cards; -using Microsoft.Teams.Apps; using Microsoft.Teams.Apps.Activities; using Microsoft.Teams.Apps.Activities.Invokes; -using Microsoft.Teams.Apps.Annotations; using Microsoft.Teams.Apps.Extensions; using Microsoft.Teams.Cards; +using Microsoft.Teams.Common; using Microsoft.Teams.Plugins.AspNetCore.DevTools.Extensions; using Microsoft.Teams.Plugins.AspNetCore.Extensions; -namespace Samples.MessageExtensions; +var builder = WebApplication.CreateBuilder(args); +builder.AddTeams().AddTeamsDevTools(); -public static partial class Program -{ - public static void Main(string[] args) - { - var builder = WebApplication.CreateBuilder(args); - builder.Services.AddTransient(); - builder.AddTeams().AddTeamsDevTools(); +var app = builder.Build(); - var app = builder.Build(); +app.UseHttpsRedirection(); - app.UseHttpsRedirection(); - - // Log raw requests - app.Use(async (context, next) => - { - if (context.Request.Method == "POST") - { - context.Request.EnableBuffering(); - var body = await new StreamReader(context.Request.Body).ReadToEndAsync(); - context.Request.Body.Position = 0; +// Log raw requests +app.Use(async (context, next) => +{ + if (context.Request.Method == "POST") + { + context.Request.EnableBuffering(); + var body = await new StreamReader(context.Request.Body).ReadToEndAsync(); + context.Request.Body.Position = 0; + Console.WriteLine($"[RAW_REQUEST] {context.Request.Method} {context.Request.Path}: {body}"); + } + await next(); +}); - // var logger = context.RequestServices.GetRequiredService>(); - Console.WriteLine($"[RAW_REQUEST] {context.Request.Method} {context.Request.Path}: {body}"); - } +var teams = app.UseTeams(); - await next(); - }); +// Serve settings page +app.MapGet("/settings", () => Results.Content(GetSettingsHtml(), "text/html")); - app.UseTeams(); +teams.OnMessage(async context => +{ + var activity = context.Activity; + context.Log.Info($"[MESSAGE] Received: {SanitizeForLog(activity.Text)}"); + context.Log.Info($"[MESSAGE] From: {SanitizeForLog(activity.From?.Name ?? "unknown")}"); + await context.Send($"Echo: {activity.Text}\n\nThis is a message extension bot. Use the message extension commands in Teams to test functionality."); +}); - // Serve settings page - app.MapGet("/settings", () => Results.Content(GetSettingsHtml(), "text/html")); +teams.OnQuery(context => +{ + context.Log.Info("[MESSAGE_EXT_QUERY] Search query received"); + var activity = context.Activity; + var commandId = activity.Value?.CommandId; + var query = activity.Value?.Parameters?.FirstOrDefault(p => p.Name == "searchQuery")?.Value?.ToString() ?? ""; + context.Log.Info($"[MESSAGE_EXT_QUERY] Command: {commandId}, Query: {query}"); - app.Run(); + if (commandId == "searchQuery") + { + return Task.FromResult(CreateSearchResults(query, context.Log)); } - [TeamsController] - public class Controller + return Task.FromResult(new Microsoft.Teams.Api.MessageExtensions.Response { - private readonly IConfiguration _configuration; - - public Controller(IConfiguration configuration) + ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result { - _configuration = configuration; + Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Result, + AttachmentLayout = Microsoft.Teams.Api.Attachment.Layout.List, + Attachments = new List() } + }); +}); - [Message] - public async System.Threading.Tasks.Task OnMessage([Context] Microsoft.Teams.Api.Activities.MessageActivity activity, [Context] IContext.Client client, [Context] Microsoft.Teams.Common.Logging.ILogger log) - { - log.Info($"[MESSAGE] Received: {SanitizeForLog(activity.Text)}"); - log.Info($"[MESSAGE] From: {SanitizeForLog(activity.From?.Name ?? "unknown")}"); - - await client.Send($"Echo: {activity.Text}\n\nThis is a message extension bot. Use the message extension commands in Teams to test functionality."); - } +teams.OnSubmitAction(context => +{ + context.Log.Info("[MESSAGE_EXT_SUBMIT] Action submit received"); + var activity = context.Activity; + var commandId = activity.Value?.CommandId; + var data = activity.Value?.Data as JsonElement?; - [MessageExtension.Query] - public Microsoft.Teams.Api.MessageExtensions.Response OnMessageExtensionQuery( - [Context] Microsoft.Teams.Api.Activities.Invokes.MessageExtensions.QueryActivity activity, - [Context] IContext.Client client, - [Context] Microsoft.Teams.Common.Logging.ILogger log) - { - log.Info("[MESSAGE_EXT_QUERY] Search query received"); + context.Log.Info($"[MESSAGE_EXT_SUBMIT] Command: {commandId}"); + context.Log.Info($"[MESSAGE_EXT_SUBMIT] Data: {JsonSerializer.Serialize(data)}"); - var commandId = activity.Value?.CommandId; - var query = activity.Value?.Parameters?.FirstOrDefault(p => p.Name == "searchQuery")?.Value?.ToString() ?? ""; + var response = commandId switch + { + "createCard" => HandleCreateCard(data, context.Log), + "getMessageDetails" => HandleGetMessageDetails(activity, context.Log), + _ => CreateErrorActionResponse("Unknown command") + }; + return Task.FromResult(response); +}); + +teams.OnQueryLink(context => +{ + context.Log.Info("[MESSAGE_EXT_QUERY_LINK] Link unfurling received"); + var activity = context.Activity; + var url = activity.Value?.Url; + context.Log.Info($"[MESSAGE_EXT_QUERY_LINK] URL: {url}"); - log.Info($"[MESSAGE_EXT_QUERY] Command: {commandId}, Query: {query}"); + if (string.IsNullOrEmpty(url)) + { + return Task.FromResult(CreateErrorResponse("No URL provided")); + } - if (commandId == "searchQuery") - { - return CreateSearchResults(query, log); - } + return Task.FromResult(CreateLinkUnfurlResponse(url, context.Log)); +}); - return new Microsoft.Teams.Api.MessageExtensions.Response - { - ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result - { - Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Result, - AttachmentLayout = Microsoft.Teams.Api.Attachment.Layout.List, - Attachments = new List() - } - }; +teams.OnSelectItem(context => +{ + context.Log.Info("[MESSAGE_EXT_SELECT_ITEM] Item selection received"); + var activity = context.Activity; + var selectedItem = activity.Value; + context.Log.Info($"[MESSAGE_EXT_SELECT_ITEM] Selected: {JsonSerializer.Serialize(selectedItem)}"); + return Task.FromResult(CreateItemSelectionResponse(selectedItem, context.Log)); +}); + +teams.OnQuerySettingsUrl(context => +{ + context.Log.Info("[MESSAGE_EXT_QUERY_SETTINGS_URL] Settings URL requested"); + return Task.FromResult(new Microsoft.Teams.Api.MessageExtensions.Response + { + ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result + { + Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Config, + Text = "Settings configuration would be handled here" } + }); +}); - [MessageExtension.SubmitAction] - public Microsoft.Teams.Api.MessageExtensions.Response OnMessageExtensionSubmit( - [Context] Microsoft.Teams.Api.Activities.Invokes.MessageExtensions.SubmitActionActivity activity, - [Context] IContext.Client client, - [Context] Microsoft.Teams.Common.Logging.ILogger log) - { - log.Info("[MESSAGE_EXT_SUBMIT] Action submit received"); +teams.OnFetchTask(context => +{ + context.Log.Info("[MESSAGE_EXT_FETCH_TASK] Fetch task received"); + var activity = context.Activity; + var commandId = activity.Value?.CommandId; + context.Log.Info($"[MESSAGE_EXT_FETCH_TASK] Command: {commandId}"); + return Task.FromResult(CreateFetchTaskResponse(commandId, context.Log)); +}); + +teams.OnSetting(context => +{ + context.Log.Info("[MESSAGE_EXT_SETTING] Settings received"); + var activity = context.Activity; + var state = activity.Value?.State; + context.Log.Info($"[MESSAGE_EXT_SETTING] State: {state}"); - var commandId = activity.Value?.CommandId; - var data = activity.Value?.Data as JsonElement?; + if (state == "cancel") + { + context.Log.Info("[MESSAGE_EXT_SETTING] Settings cancelled by user"); + } + else + { + context.Log.Info("[MESSAGE_EXT_SETTING] Settings processing completed"); + } - log.Info($"[MESSAGE_EXT_SUBMIT] Command: {commandId}"); - log.Info($"[MESSAGE_EXT_SUBMIT] Data: {JsonSerializer.Serialize(data)}"); + return Task.CompletedTask; +}); - switch (commandId) - { - case "createCard": - return HandleCreateCard(data, log); +app.Run(); - case "getMessageDetails": - return HandleGetMessageDetails(activity, log); +static string SanitizeForLog(string? input) +{ + if (input == null) return ""; + return input.Replace("\r", "").Replace("\n", ""); +} - default: - log.Error($"[MESSAGE_EXT_SUBMIT] Unknown command: {commandId}"); - return CreateErrorActionResponse("Unknown command"); - } - } +static Microsoft.Teams.Api.MessageExtensions.Response CreateSearchResults(string query, Microsoft.Teams.Common.Logging.ILogger log) +{ + var attachments = new List(); - [MessageExtension.QueryLink] - public Microsoft.Teams.Api.MessageExtensions.Response OnMessageExtensionQueryLink( - [Context] Microsoft.Teams.Api.Activities.Invokes.MessageExtensions.QueryLinkActivity activity, - [Context] IContext.Client client, - [Context] Microsoft.Teams.Common.Logging.ILogger log) + for (int i = 1; i <= 5; i++) + { + var card = new Microsoft.Teams.Cards.AdaptiveCard { - log.Info("[MESSAGE_EXT_QUERY_LINK] Link unfurling received"); - - var url = activity.Value?.Url; - log.Info($"[MESSAGE_EXT_QUERY_LINK] URL: {url}"); - - if (string.IsNullOrEmpty(url)) + Body = new List { - return CreateErrorResponse("No URL provided"); + new TextBlock($"Search Result {i}") { Weight = TextWeight.Bolder, Size = TextSize.Large }, + new TextBlock($"Query: '{query}' - Result description for item {i}") { Wrap = true, IsSubtle = true } } + }; - return CreateLinkUnfurlResponse(url, log); - } - - [MessageExtension.SelectItem] - public Microsoft.Teams.Api.MessageExtensions.Response OnMessageExtensionSelectItem( - [Context] Microsoft.Teams.Api.Activities.Invokes.MessageExtensions.SelectItemActivity activity, - [Context] IContext.Client client, - [Context] Microsoft.Teams.Common.Logging.ILogger log) + var previewCard = new ThumbnailCard() { - log.Info("[MESSAGE_EXT_SELECT_ITEM] Item selection received"); - - var selectedItem = activity.Value; - log.Info($"[MESSAGE_EXT_SELECT_ITEM] Selected: {JsonSerializer.Serialize(selectedItem)}"); - - return CreateItemSelectionResponse(selectedItem, log); - } + Title = $"Result {i}", + Text = $"This is a preview of result {i} for query '{query}'." + }; - [MessageExtension.QuerySettingsUrl] - public Microsoft.Teams.Api.MessageExtensions.Response OnMessageExtensionQuerySettingsUrl( - [Context] Microsoft.Teams.Api.Activities.Invokes.MessageExtensions.QuerySettingUrlActivity activity, - [Context] IContext.Client client, - [Context] Microsoft.Teams.Common.Logging.ILogger log) + var attachment = new Microsoft.Teams.Api.MessageExtensions.Attachment { - log.Info("[MESSAGE_EXT_QUERY_SETTINGS_URL] Settings URL requested"); - - return new Microsoft.Teams.Api.MessageExtensions.Response + ContentType = Microsoft.Teams.Api.ContentType.AdaptiveCard, + Content = card, + Preview = new Microsoft.Teams.Api.MessageExtensions.Attachment { - ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result - { - Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Config, - Text = "Settings configuration would be handled here" - } - }; - } - - [MessageExtension.FetchTask] - public Microsoft.Teams.Api.MessageExtensions.ActionResponse OnMessageExtensionFetchTask( - [Context] Microsoft.Teams.Api.Activities.Invokes.MessageExtensions.FetchTaskActivity activity, - [Context] Microsoft.Teams.Common.Logging.ILogger log) - { - log.Info("[MESSAGE_EXT_FETCH_TASK] Fetch task received"); - - var commandId = activity.Value?.CommandId; - log.Info($"[MESSAGE_EXT_FETCH_TASK] Command: {commandId}"); + ContentType = Microsoft.Teams.Api.ContentType.ThumbnailCard, + Content = previewCard + } + }; - return CreateFetchTaskResponse(commandId, log); - } + attachments.Add(attachment); + } - [MessageExtension.Setting] - public Microsoft.Teams.Api.MessageExtensions.Response OnMessageExtensionSetting( - [Context] Microsoft.Teams.Api.Activities.Invokes.MessageExtensions.SettingActivity activity, - [Context] IContext.Client client, - [Context] Microsoft.Teams.Common.Logging.ILogger log) + return new Microsoft.Teams.Api.MessageExtensions.Response + { + ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result { - log.Info("[MESSAGE_EXT_SETTING] Settings received"); - - var state = activity.Value?.State; - log.Info($"[MESSAGE_EXT_SETTING] State: {state}"); + Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Result, + AttachmentLayout = Microsoft.Teams.Api.Attachment.Layout.List, + Attachments = attachments + } + }; +} - if (state == "cancel") - { - log.Info("[MESSAGE_EXT_SETTING] Settings cancelled by user"); - return new Microsoft.Teams.Api.MessageExtensions.Response(); - } +static Microsoft.Teams.Api.MessageExtensions.Response HandleCreateCard(JsonElement? data, Microsoft.Teams.Common.Logging.ILogger log) +{ + var title = GetJsonValue(data, "title") ?? "Default Title"; + var description = GetJsonValue(data, "description") ?? "Default Description"; - // Process settings data - // Note: Settings property may not be available in current API - log.Info("[MESSAGE_EXT_SETTING] Settings processing completed"); + log.Info($"[CREATE_CARD] Title: {title}, Description: {description}"); - return new Microsoft.Teams.Api.MessageExtensions.Response(); + var card = new Microsoft.Teams.Cards.AdaptiveCard + { + Schema = "http://adaptivecards.io/schemas/adaptive-card.json", + Body = new List + { + new TextBlock("Custom Card Created") { Weight = TextWeight.Bolder, Size = TextSize.Large, Color = TextColor.Good }, + new TextBlock(title) { Weight = TextWeight.Bolder, Size = TextSize.Medium }, + new TextBlock(description) { Wrap = true, IsSubtle = true } } + }; + + var attachment = new Microsoft.Teams.Api.MessageExtensions.Attachment + { + ContentType = Microsoft.Teams.Api.ContentType.AdaptiveCard, + Content = card + }; - // Helper methods for creating responses - private static Microsoft.Teams.Api.MessageExtensions.Response CreateSearchResults(string query, Microsoft.Teams.Common.Logging.ILogger log) + return new Microsoft.Teams.Api.MessageExtensions.Response + { + ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result { - var attachments = new List(); + Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Result, + AttachmentLayout = Microsoft.Teams.Api.Attachment.Layout.List, + Attachments = new List { attachment } + } + }; +} - // Create simple search results - for (int i = 1; i <= 5; i++) - { - var card = new Microsoft.Teams.Cards.AdaptiveCard - { - Body = new List - { - new TextBlock($"Search Result {i}") - { - Weight = TextWeight.Bolder, - Size = TextSize.Large - }, - new TextBlock($"Query: '{query}' - Result description for item {i}") - { - Wrap = true, - IsSubtle = true - } - } - }; - - var previewCard = new ThumbnailCard() - { - Title = $"Result {i}", - Text = $"This is a preview of result {i} for query '{query}'." - }; - - var attachment = new Microsoft.Teams.Api.MessageExtensions.Attachment - { - ContentType = Microsoft.Teams.Api.ContentType.AdaptiveCard, - Content = card, - Preview = new Microsoft.Teams.Api.MessageExtensions.Attachment - { - ContentType = Microsoft.Teams.Api.ContentType.ThumbnailCard, - Content = previewCard - } - }; - - attachments.Add(attachment); - } +static Microsoft.Teams.Api.MessageExtensions.Response HandleGetMessageDetails(Microsoft.Teams.Api.Activities.Invokes.MessageExtensions.SubmitActionActivity activity, Microsoft.Teams.Common.Logging.ILogger log) +{ + var messageText = activity.Value?.MessagePayload?.Body?.Content ?? "No message content"; + var messageId = activity.Value?.MessagePayload?.Id ?? "Unknown"; - return new Microsoft.Teams.Api.MessageExtensions.Response - { - ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result - { - Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Result, - AttachmentLayout = Microsoft.Teams.Api.Attachment.Layout.List, - Attachments = attachments - } - }; - } + log.Info($"[GET_MESSAGE_DETAILS] Message ID: {messageId}"); - private static Microsoft.Teams.Api.MessageExtensions.Response HandleCreateCard(JsonElement? data, Microsoft.Teams.Common.Logging.ILogger log) + var card = new Microsoft.Teams.Cards.AdaptiveCard + { + Schema = "http://adaptivecards.io/schemas/adaptive-card.json", + Body = new List { - var title = GetJsonValue(data, "title") ?? "Default Title"; - var description = GetJsonValue(data, "description") ?? "Default Description"; - - log.Info($"[CREATE_CARD] Title: {title}, Description: {description}"); + new TextBlock("Message Details") { Weight = TextWeight.Bolder, Size = TextSize.Large, Color = TextColor.Accent }, + new TextBlock($"Message ID: {messageId}") { Wrap = true }, + new TextBlock($"Content: {messageText}") { Wrap = true } + } + }; - var card = new Microsoft.Teams.Cards.AdaptiveCard - { - Schema = "http://adaptivecards.io/schemas/adaptive-card.json", - Body = new List - { - new TextBlock("Custom Card Created") - { - Weight = TextWeight.Bolder, - Size = TextSize.Large, - Color = TextColor.Good - }, - new TextBlock(title) - { - Weight = TextWeight.Bolder, - Size = TextSize.Medium - }, - new TextBlock(description) - { - Wrap = true, - IsSubtle = true - } - } - }; - - var attachment = new Microsoft.Teams.Api.MessageExtensions.Attachment - { - ContentType = Microsoft.Teams.Api.ContentType.AdaptiveCard, - Content = card - }; + var attachment = new Microsoft.Teams.Api.MessageExtensions.Attachment + { + ContentType = new Microsoft.Teams.Api.ContentType("application/vnd.microsoft.card.adaptive"), + Content = card + }; - return new Microsoft.Teams.Api.MessageExtensions.Response - { - ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result - { - Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Result, - AttachmentLayout = Microsoft.Teams.Api.Attachment.Layout.List, - Attachments = new List { attachment } - } - }; + return new Microsoft.Teams.Api.MessageExtensions.Response + { + ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result + { + Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Result, + AttachmentLayout = Microsoft.Teams.Api.Attachment.Layout.List, + Attachments = new List { attachment } } + }; +} - private static Microsoft.Teams.Api.MessageExtensions.Response HandleGetMessageDetails(Microsoft.Teams.Api.Activities.Invokes.MessageExtensions.SubmitActionActivity activity, Microsoft.Teams.Common.Logging.ILogger log) +static Microsoft.Teams.Api.MessageExtensions.Response CreateLinkUnfurlResponse(string url, Microsoft.Teams.Common.Logging.ILogger log) +{ + var card = new Microsoft.Teams.Cards.AdaptiveCard + { + Schema = "http://adaptivecards.io/schemas/adaptive-card.json", + Body = new List { - var messageText = activity.Value?.MessagePayload?.Body?.Content ?? "No message content"; - var messageId = activity.Value?.MessagePayload?.Id ?? "Unknown"; - - log.Info($"[GET_MESSAGE_DETAILS] Message ID: {messageId}"); - - var card = new Microsoft.Teams.Cards.AdaptiveCard - { - Schema = "http://adaptivecards.io/schemas/adaptive-card.json", - Body = new List - { - new TextBlock("Message Details") - { - Weight = TextWeight.Bolder, - Size = TextSize.Large, - Color = TextColor.Accent - }, - new TextBlock($"Message ID: {messageId}") - { - Wrap = true - }, - new TextBlock($"Content: {messageText}") - { - Wrap = true - } - } - }; - - var attachment = new Microsoft.Teams.Api.MessageExtensions.Attachment - { - ContentType = new Microsoft.Teams.Api.ContentType("application/vnd.microsoft.card.adaptive"), - Content = card - }; - - return new Microsoft.Teams.Api.MessageExtensions.Response - { - ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result - { - Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Result, - AttachmentLayout = Microsoft.Teams.Api.Attachment.Layout.List, - Attachments = new List { attachment } - } - }; + new TextBlock("Link Preview") { Weight = TextWeight.Bolder, Size = TextSize.Medium }, + new TextBlock($"URL: {url}") { IsSubtle = true, Wrap = true }, + new TextBlock("This is a preview of the linked content generated by the message extension.") { Wrap = true, Size = TextSize.Small } } + }; - private static Microsoft.Teams.Api.MessageExtensions.Response CreateLinkUnfurlResponse(string url, Microsoft.Teams.Common.Logging.ILogger log) + var attachment = new Microsoft.Teams.Api.MessageExtensions.Attachment + { + ContentType = new Microsoft.Teams.Api.ContentType("application/vnd.microsoft.card.adaptive"), + Content = card, + Preview = new Microsoft.Teams.Api.MessageExtensions.Attachment { - var card = new Microsoft.Teams.Cards.AdaptiveCard - { - Schema = "http://adaptivecards.io/schemas/adaptive-card.json", - Body = new List - { - new TextBlock("Link Preview") - { - Weight = TextWeight.Bolder, - Size = TextSize.Medium - }, - new TextBlock($"URL: {url}") - { - IsSubtle = true, - Wrap = true - }, - new TextBlock("This is a preview of the linked content generated by the message extension.") - { - Wrap = true, - Size = TextSize.Small - } - } - }; - - var attachment = new Microsoft.Teams.Api.MessageExtensions.Attachment - { - ContentType = new Microsoft.Teams.Api.ContentType("application/vnd.microsoft.card.adaptive"), - Content = card, - Preview = new Microsoft.Teams.Api.MessageExtensions.Attachment - { - ContentType = Microsoft.Teams.Api.ContentType.ThumbnailCard, - Content = new ThumbnailCard - { - Title = "Link Preview", - Text = url - } - } - }; - - return new Microsoft.Teams.Api.MessageExtensions.Response - { - ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result - { - Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Result, - AttachmentLayout = Microsoft.Teams.Api.Attachment.Layout.List, - Attachments = new List { attachment } - } - }; + ContentType = Microsoft.Teams.Api.ContentType.ThumbnailCard, + Content = new ThumbnailCard { Title = "Link Preview", Text = url } } + }; - private static Microsoft.Teams.Api.MessageExtensions.Response CreateItemSelectionResponse(object? selectedItem, Microsoft.Teams.Common.Logging.ILogger log) + return new Microsoft.Teams.Api.MessageExtensions.Response + { + ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result { - var itemJson = JsonSerializer.Serialize(selectedItem); + Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Result, + AttachmentLayout = Microsoft.Teams.Api.Attachment.Layout.List, + Attachments = new List { attachment } + } + }; +} - var card = new Microsoft.Teams.Cards.AdaptiveCard - { - Schema = "http://adaptivecards.io/schemas/adaptive-card.json", - Body = new List - { - new TextBlock("Item Selected") - { - Weight = TextWeight.Bolder, - Size = TextSize.Large, - Color = TextColor.Good - }, - new TextBlock("You selected the following item:") - { - Wrap = true - }, - new TextBlock(itemJson) - { - Wrap = true, - FontType = FontType.Monospace, - Separator = true - } - } - }; - - var attachment = new Microsoft.Teams.Api.MessageExtensions.Attachment - { - ContentType = new Microsoft.Teams.Api.ContentType("application/vnd.microsoft.card.adaptive"), - Content = card - }; +static Microsoft.Teams.Api.MessageExtensions.Response CreateItemSelectionResponse(object? selectedItem, Microsoft.Teams.Common.Logging.ILogger log) +{ + var itemJson = JsonSerializer.Serialize(selectedItem); - return new Microsoft.Teams.Api.MessageExtensions.Response - { - ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result - { - Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Result, - AttachmentLayout = Microsoft.Teams.Api.Attachment.Layout.List, - Attachments = new List { attachment } - } - }; + var card = new Microsoft.Teams.Cards.AdaptiveCard + { + Schema = "http://adaptivecards.io/schemas/adaptive-card.json", + Body = new List + { + new TextBlock("Item Selected") { Weight = TextWeight.Bolder, Size = TextSize.Large, Color = TextColor.Good }, + new TextBlock("You selected the following item:") { Wrap = true }, + new TextBlock(itemJson) { Wrap = true, FontType = FontType.Monospace, Separator = true } } + }; + + var attachment = new Microsoft.Teams.Api.MessageExtensions.Attachment + { + ContentType = new Microsoft.Teams.Api.ContentType("application/vnd.microsoft.card.adaptive"), + Content = card + }; - private static Microsoft.Teams.Api.MessageExtensions.Response CreateErrorResponse(string message) + return new Microsoft.Teams.Api.MessageExtensions.Response + { + ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result { - return new Microsoft.Teams.Api.MessageExtensions.Response - { - ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result - { - Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Message, - Text = message - } - }; + Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Result, + AttachmentLayout = Microsoft.Teams.Api.Attachment.Layout.List, + Attachments = new List { attachment } } + }; +} - private static Microsoft.Teams.Api.MessageExtensions.Response CreateErrorActionResponse(string message) +static Microsoft.Teams.Api.MessageExtensions.Response CreateErrorResponse(string message) +{ + return new Microsoft.Teams.Api.MessageExtensions.Response + { + ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result { - return new Microsoft.Teams.Api.MessageExtensions.Response - { - ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result - { - Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Message, - Text = message - } - }; + Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Message, + Text = message } + }; +} - private static string? GetJsonValue(JsonElement? data, string key) +static Microsoft.Teams.Api.MessageExtensions.Response CreateErrorActionResponse(string message) +{ + return new Microsoft.Teams.Api.MessageExtensions.Response + { + ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result { - if (data?.ValueKind == JsonValueKind.Object && data.Value.TryGetProperty(key, out var value)) - { - return value.GetString(); - } - return null; + Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Message, + Text = message } + }; +} - private static Microsoft.Teams.Api.MessageExtensions.ActionResponse CreateFetchTaskResponse(string? commandId, Microsoft.Teams.Common.Logging.ILogger log) - { - log.Info($"[CREATE_FETCH_TASK] Creating task for command: {commandId}"); - // Updated to use actual converation members +static string? GetJsonValue(JsonElement? data, string key) +{ + if (data?.ValueKind == JsonValueKind.Object && data.Value.TryGetProperty(key, out var value)) + { + return value.GetString(); + } + return null; +} - // Create an adaptive card for the task module - var card = new Microsoft.Teams.Cards.AdaptiveCard - { - Body = new List - { - new TextBlock("Conversation Members is not implemented in C# yet :(") - { - Weight = TextWeight.Bolder, - Color = TextColor.Accent - }, - } - }; - - return new Microsoft.Teams.Api.MessageExtensions.ActionResponse - { - Task = new Microsoft.Teams.Api.TaskModules.ContinueTask(new Microsoft.Teams.Api.TaskModules.TaskInfo - { - Title = "Fetch Task Dialog", - Height = new Microsoft.Teams.Common.Union(Microsoft.Teams.Api.TaskModules.Size.Small), - Width = new Microsoft.Teams.Common.Union(Microsoft.Teams.Api.TaskModules.Size.Small), - Card = new Microsoft.Teams.Api.Attachment(card) - }) - }; - } +static Microsoft.Teams.Api.MessageExtensions.ActionResponse CreateFetchTaskResponse(string? commandId, Microsoft.Teams.Common.Logging.ILogger log) +{ + log.Info($"[CREATE_FETCH_TASK] Creating task for command: {commandId}"); - private static string SanitizeForLog(string? input) + var card = new Microsoft.Teams.Cards.AdaptiveCard + { + Body = new List { - if (input == null) return ""; - return input.Replace("\r", "").Replace("\n", ""); + new TextBlock("Conversation Members is not implemented in C# yet :(") + { + Weight = TextWeight.Bolder, + Color = TextColor.Accent + }, } - } + }; - private static string GetSettingsHtml() + return new Microsoft.Teams.Api.MessageExtensions.ActionResponse { - return """ + Task = new Microsoft.Teams.Api.TaskModules.ContinueTask(new Microsoft.Teams.Api.TaskModules.TaskInfo + { + Title = "Fetch Task Dialog", + Height = new Union(Microsoft.Teams.Api.TaskModules.Size.Small), + Width = new Union(Microsoft.Teams.Api.TaskModules.Size.Small), + Card = new Microsoft.Teams.Api.Attachment(card) + }) + }; +} + +static string GetSettingsHtml() +{ + return """ @@ -633,5 +507,4 @@ function cancelSettings() { """; - } -} \ No newline at end of file +}