1+ @using System .Diagnostics
2+ @using System .Net .Security
3+ @using System .Linq
4+ @using System .Text
5+ @using HeyRed .MarkdownSharp
6+ @using Microsoft .Extensions .AI
7+ @using Microsoft .Extensions .Logging
8+ @using Microsoft .Extensions .Primitives
9+ @using Return .Application .Common .Models
10+ @inherits Return .Web .Components .ShowcaseBase
11+ @inject IChatClient ChatClient
12+
13+ @if (this .Data is null )
14+ {
15+ <p >Loading .. .</p >
16+ return ;
17+ }
18+
19+ <button type =" button"
20+ class =" button is-link is-outlined full-width"
21+ data-test-element-id =" toggle-view-button"
22+ disabled =" @IsLoadingSummary"
23+ @onclick =" @(this.LoadSummary)" >
24+ <span class =" icon" ><span class =" fa-solid fa-quote-left" ></span ></span ><span >Summarize this retrospective</span >
25+ </button >
26+
27+ @if (Summary is null )
28+ {
29+ <p ></p >
30+ } else if (IsLoadingSummary && Summary is null )
31+ {
32+ <p class =" summarizer" >
33+ <i class =" fa-solid fa-gears" ></i >
34+ <i >Thinking .. .</i >
35+ </p >
36+ }
37+ else
38+ {
39+ <div class =" summarizer" >
40+ @if (IsLoadingSummary ) {
41+ < i class = " fa-solid fa-gears" >< / i >
42+ < text > & nbsp ;< / text >
43+ }
44+
45+ @{
46+ MarkupString summaryHtml = new (Summary );
47+ }
48+
49+ @summaryHtml
50+ </div >
51+ }
52+
53+ @code {
54+ private bool IsLoadingSummary { get ; set ; }
55+ private string ? Summary { get ; set ; }
56+
57+ private async Task LoadSummary ()
58+ {
59+ if (this .IsLoadingSummary || this .Data == null ) return ;
60+
61+ this .IsLoadingSummary = true ;
62+ Summary = null ;
63+ StateHasChanged ();
64+
65+ ChatOptions chatOptions = new ()
66+ {
67+ Temperature = 0 . 2 f ,
68+ ResponseFormat = ChatResponseFormat .Text ,
69+ };
70+
71+ string laneNames = String .Join (" /" , this .Data .Items .Select (x => x .Lane .Name ).Distinct ());
72+ List <ChatMessage > chatMessages =
73+ [
74+ new (
75+ ChatRole .System ,
76+ $@" A retrospective has been performed. Summarize the following messages, prefixed with categories '{laneNames }', in a maximum of 60 words per category. Spend more words on highly voted notes and less on lower voted notes.
77+ Don't call out individual notes, just summarize the general sentiment. Limit making up additional context or words, keep it to the given messages. Use markdown syntax for headings, subheadings and lists.
78+ "
79+ )
80+ ];
81+
82+ Logger .LogInformation (" Compose system message: {Msg}" , chatMessages [0 ].Text );
83+
84+ var byLane =
85+ from item in this .Data .Items
86+ group item by item .Lane .Name into g
87+ select new { Lane = g .Key , Notes = g .OrderByDescending (x => x .Votes .Count ) };
88+
89+ StringBuilder msgBuilder = new ();
90+ foreach (var group in byLane )
91+ {
92+ foreach (var item in group .Notes )
93+ {
94+ msgBuilder .Clear ();
95+
96+ msgBuilder .Append ($" Category \" {item .Lane .Name }\" " );
97+
98+ if (item is { NoteGroup : { } ng })
99+ {
100+ msgBuilder .AppendLine ($" - multiple items grouped \" {item .NoteGroup .Title }\" (voted {item .Votes .Count } times): " );
101+ foreach (RetrospectiveNote note in ng .Notes )
102+ {
103+ msgBuilder .Append ($" - {note .Text }" );
104+ }
105+ }
106+
107+ if (item is { Note : { } n })
108+ {
109+ msgBuilder .AppendLine ($" - voted {item .Votes .Count } times: " );
110+ msgBuilder .AppendLine (n .Text );
111+ }
112+
113+ chatMessages .Add (
114+ new (
115+ ChatRole .User ,
116+ msgBuilder .ToString ()
117+ )
118+ );
119+
120+ Logger .LogDebug (" Composed sub-chatmessage: {Msg}" , msgBuilder .ToString ());
121+ }
122+ }
123+
124+ long startTime = Stopwatch .GetTimestamp ();
125+
126+ Logger .LogDebug (" Invoking AI with {Count} messages" , chatMessages .Count );
127+ try
128+ {
129+ Summary = null ;
130+
131+ Markdown markdown = new ();
132+ string rawSummary = String .Empty ;
133+
134+ await foreach (StreamingChatCompletionUpdate subText in this .ChatClient .CompleteStreamingAsync (chatMessages , chatOptions ))
135+ {
136+ // Logger.LogTrace("Streamed sub text: {@Update}", subText.Text);
137+
138+ if (subText is { Text : { } text })
139+ {
140+ rawSummary += text ;
141+ this .Summary = markdown .Transform (rawSummary );
142+
143+ StateHasChanged ();
144+ }
145+ }
146+
147+ this .Summary = markdown .Transform (rawSummary );
148+ Logger .LogTrace (" Total generated summary: {RawSummary}" , rawSummary );
149+ }
150+ catch (Exception ex )
151+ {
152+ Logger .LogError (ex , " Error invoking AI" );
153+ Summary = ex .ToString ();
154+ }
155+
156+ Logger .LogDebug (" Completed AI invocation in {Elapsed}" , Stopwatch .GetElapsedTime (startTime ));
157+ IsLoadingSummary = false ;
158+ }
159+
160+ }
0 commit comments