microsoft/teams.net

Public

mirrored fromhttps://github.com/microsoft/teams.netAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
next/core

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

Samples/Samples.AI/Handlers/FeedbackHandler.cs

208lines · modecode

1using System.Collections.Concurrent;
2
3using Microsoft.Teams.AI.Models.OpenAI;
4using Microsoft.Teams.AI.Prompts;
5using Microsoft.Teams.AI.Templates;
6using Microsoft.Teams.Api;
7using Microsoft.Teams.Api.Activities.Invokes;
8using Microsoft.Teams.Apps;
9using Microsoft.Teams.Cards;
10
11using TaskModules = Microsoft.Teams.Api.TaskModules;
12
13namespace Samples.AI.Handlers;
14
15/// <summary>
16/// Handles feedback loop functionality - sending AI messages with feedback buttons
17/// and processing feedback submissions
18/// </summary>
19public static class FeedbackHandler
20{
21 /// <summary>
22 /// Storage for feedback data. In production, this would be persisted in a database.
23 /// </summary>
24 public static readonly ConcurrentDictionary<string, FeedbackData> StoredFeedbackByMessageId = new();
25
26 /// <summary>
27 /// Handles the feedback loop command - sends an AI response with feedback buttons
28 /// </summary>
29 public static async Task HandleFeedbackLoop(OpenAIChatModel model, IContext<Microsoft.Teams.Api.Activities.MessageActivity> context, CancellationToken cancellationToken = default)
30 {
31 context.Log.Info($"[HANDLER] Feedback loop handler invoked with query: {context.Activity.Text}");
32
33 var prompt = new OpenAIChatPrompt(model, new ChatPromptOptions
34 {
35 Instructions = new StringTemplate("You are a helpful assistant.")
36 });
37
38 context.Log.Info("[HANDLER] Sending query to AI model...");
39 var result = await prompt.Send(context.Activity.Text, cancellationToken);
40
41 if (result.Content != null)
42 {
43 context.Log.Info($"[HANDLER] AI response received: {result.Content}");
44
45 // Create message with AI generated indicator and custom feedback buttons.
46 // Clicking a feedback button in 'custom' mode triggers a message/fetchTask
47 // invoke (handled by HandleFeedbackFetchTask) so the bot can show its own dialog.
48 var messageActivity = new Microsoft.Teams.Api.Activities.MessageActivity
49 {
50 Text = result.Content,
51 }
52 .AddAIGenerated()
53 .AddFeedback(FeedbackType.Custom);
54
55 context.Log.Info("[HANDLER] Sending message with feedback buttons");
56 var sentActivity = await context.Send(messageActivity, cancellationToken);
57
58 // Store the feedback data for later retrieval
59 if (sentActivity.Id != null)
60 {
61 StoredFeedbackByMessageId[sentActivity.Id] = new FeedbackData
62 {
63 IncomingMessage = context.Activity.Text ?? string.Empty,
64 OutgoingMessage = result.Content,
65 Likes = 0,
66 Dislikes = 0,
67 Feedbacks = new List<string>()
68 };
69 context.Log.Info($"[HANDLER] Stored feedback data for message ID: {sentActivity.Id}");
70 }
71 }
72 else
73 {
74 context.Log.Warn("[HANDLER] AI did not generate a response");
75 await context.Reply("I did not generate a response.", cancellationToken);
76 }
77 }
78
79 /// <summary>
80 /// Builds the task module (dialog) shown when the user clicks a feedback
81 /// button on a message whose feedback loop type is <c>custom</c>.
82 /// </summary>
83 public static TaskModules.Response HandleFeedbackFetchTask(IContext<Messages.FetchTaskActivity> context)
84 {
85 var reaction = context.Activity.Value.Data.ActionValue.Reaction;
86 context.Log.Info($"[HANDLER] Feedback fetch-task invoked, reaction: {reaction}");
87
88 var card = new AdaptiveCard
89 {
90 Schema = "http://adaptivecards.io/schemas/adaptive-card.json",
91 Body = new List<CardElement>
92 {
93 new TextBlock($"You reacted {reaction}. Tell us more (optional):") { Wrap = true },
94 new TextInput
95 {
96 Id = "feedbackText",
97 Placeholder = "Your feedback...",
98 IsMultiline = true,
99 }
100 },
101 Actions = new List<Microsoft.Teams.Cards.Action>
102 {
103 new SubmitAction { Title = "Submit" }
104 }
105 };
106
107 return new TaskModules.Response(new TaskModules.ContinueTask(new TaskModules.TaskInfo
108 {
109 Title = "Feedback",
110 Card = new Attachment(card),
111 }));
112 }
113
114 /// <summary>
115 /// Handles feedback submissions from users
116 /// </summary>
117 public static void HandleFeedbackSubmission(IContext<Messages.SubmitActionActivity> context)
118 {
119 context.Log.Info($"[HANDLER] Feedback submission received for activity ID: {context.Activity.Id}");
120
121 if (context.Activity.Value?.ActionValue == null)
122 {
123 context.Log.Warn("[HANDLER] No action value found in feedback submission");
124 return;
125 }
126
127 context.Log.Info($"[HANDLER] Raw ActionValue: {System.Text.Json.JsonSerializer.Serialize(context.Activity.Value.ActionValue)}");
128
129 // Deserialize ActionValue to a dictionary
130 var actionValueJson = System.Text.Json.JsonSerializer.Serialize(context.Activity.Value.ActionValue);
131 var actionValue = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, string>>(actionValueJson);
132
133 if (actionValue == null)
134 {
135 context.Log.Warn("[HANDLER] Could not parse action value");
136 return;
137 }
138
139 // Extract reaction (like/dislike) and feedback JSON string
140 var reaction = actionValue.ContainsKey("reaction") ? actionValue["reaction"] : null;
141 var feedbackJsonString = actionValue.ContainsKey("feedback") ? actionValue["feedback"] : null;
142
143 // Parse the feedback JSON string to extract feedbackText
144 string? feedbackText = null;
145 if (!string.IsNullOrEmpty(feedbackJsonString))
146 {
147 try
148 {
149 var feedbackObj = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, string>>(feedbackJsonString);
150 feedbackText = feedbackObj?.ContainsKey("feedbackText") == true ? feedbackObj["feedbackText"] : null;
151 }
152 catch (System.Text.Json.JsonException ex)
153 {
154 context.Log.Warn($"[HANDLER] Failed to parse feedback JSON: {ex.Message}");
155 }
156 }
157
158 context.Log.Info($"[HANDLER] Reaction: {reaction}, Feedback Text: {feedbackText}");
159
160 if (context.Activity.ReplyToId == null)
161 {
162 context.Log.Warn($"[HANDLER] No replyToId found for message ID {context.Activity.Id}");
163 return;
164 }
165
166 // Update stored feedback
167 if (StoredFeedbackByMessageId.TryGetValue(context.Activity.ReplyToId, out var existingFeedback))
168 {
169 if (reaction == "like")
170 {
171 existingFeedback.Likes++;
172 context.Log.Info($"[HANDLER] Incremented likes to {existingFeedback.Likes}");
173 }
174 else if (reaction == "dislike")
175 {
176 existingFeedback.Dislikes++;
177 context.Log.Info($"[HANDLER] Incremented dislikes to {existingFeedback.Dislikes}");
178 }
179
180 if (feedbackText != null)
181 {
182 existingFeedback.Feedbacks.Add(feedbackText);
183 context.Log.Info($"[HANDLER] Added feedback text: '{feedbackText}'. Total feedbacks: {existingFeedback.Feedbacks.Count}");
184 }
185
186 // Log feedback summary
187 context.Log.Info($"[HANDLER] Feedback summary for message {context.Activity.ReplyToId}: " +
188 $"Likes={existingFeedback.Likes}, Dislikes={existingFeedback.Dislikes}, " +
189 $"Feedbacks={existingFeedback.Feedbacks.Count}");
190 }
191 else
192 {
193 context.Log.Warn($"[HANDLER] No feedback data found for message ID {context.Activity.ReplyToId}");
194 }
195 }
196}
197
198/// <summary>
199/// Data structure for storing feedback information
200/// </summary>
201public class FeedbackData
202{
203 public string IncomingMessage { get; set; } = string.Empty;
204 public string OutgoingMessage { get; set; } = string.Empty;
205 public int Likes { get; set; }
206 public int Dislikes { get; set; }
207 public List<string> Feedbacks { get; set; } = new();
208}