diff --git a/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/AI/Messages/DefaultMessageTemplate.cs b/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/AI/Messages/DefaultMessageTemplate.cs index 294e0c51b5..9729a53542 100644 --- a/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/AI/Messages/DefaultMessageTemplate.cs +++ b/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/AI/Messages/DefaultMessageTemplate.cs @@ -23,9 +23,10 @@ internal sealed class DefaultMessageTemplate : MessageTemplateBase if (messageContext.ChatMessage is not null) { var getRenderContents = messageContext.RenderContents(); + // Return a render fragment that checks visibility at render time + // This is important because message contents may change during streaming return CreateRenderMessage( - messageContext.ChatMessage.Role, - messageContext.ChatMessage.MessageId, + messageContext.ChatMessage, getRenderContents); } else @@ -44,29 +45,61 @@ internal sealed class DefaultMessageTemplate : MessageTemplateBase } return CreateRenderMessage( - this._buffer[0].Role, - this._buffer[0].MessageId, + this._buffer[0], getRenderContents); } } + /// + /// Checks if a message has any visible content that should be displayed. + /// Messages that only contain FunctionCallContent or FunctionResultContent + /// are internal tool messages and should not be rendered as chat bubbles. + /// + private static bool HasVisibleContent(ChatMessage message) + { + if (message.Contents.Count == 0) + { + return false; + } + + foreach (var content in message.Contents) + { + // TextContent is visible + if (content is TextContent textContent && !string.IsNullOrWhiteSpace(textContent.Text)) + { + return true; + } + + // Other content types that are not function calls/results are visible + if (content is not FunctionCallContent && content is not FunctionResultContent) + { + return true; + } + } + + return false; + } + private static RenderFragment CreateRenderMessage( - ChatRole role, - string? messageId, + ChatMessage message, RenderFragment getRenderContents) { - var roleClass = $"{role}-message"; + var roleClass = $"{message.Role}-message"; return builder => { + // Check visibility at render time, not at template creation time + // This is important because message contents may change during streaming + if (!HasVisibleContent(message)) + { + return; // Don't render messages without visible content + } + builder.OpenElement(0, "div"); - if (!string.IsNullOrEmpty(messageId)) + if (!string.IsNullOrEmpty(message.MessageId)) { - builder.AddAttribute(1, "id", messageId); - } - else - { - builder.AddAttribute(2, "class", $"chat-message {roleClass}"); + builder.AddAttribute(1, "id", message.MessageId); } + builder.AddAttribute(2, "class", $"chat-message {roleClass}"); builder.AddContent(3, getRenderContents); builder.CloseElement(); }; diff --git a/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/AI/Messages/MessageList.cs b/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/AI/Messages/MessageList.cs index a83d7403ab..5010f1ef3c 100644 --- a/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/AI/Messages/MessageList.cs +++ b/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/AI/Messages/MessageList.cs @@ -79,15 +79,19 @@ internal sealed partial class MessageList : IComponent, IDisposable // Track all render keys to detect duplicates var allRenderKeys = new HashSet(); + // Open container div for message list with flex layout + builder.OpenElement(0, "div"); + builder.AddAttribute(1, "class", "messages-container"); + foreach (var message in this.MessageListContext.AgentBoundaryContext.CompletedMessages) { var renderKey = GetUniqueRenderKey(message); // Calling GetTemplate will stop template collection on the first message if it // was still ongoing. - builder.OpenComponent(0); + builder.OpenComponent(2); builder.SetKey(renderKey); - builder.AddComponentParameter(1, "ChildContent", this.MessageListContext.GetTemplate(message)); + builder.AddComponentParameter(3, "ChildContent", this.MessageListContext.GetTemplate(message)); builder.CloseComponent(); } @@ -95,11 +99,14 @@ internal sealed partial class MessageList : IComponent, IDisposable { var renderKey = GetUniqueRenderKey(message); - builder.OpenComponent(1); + builder.OpenComponent(4); builder.SetKey(renderKey); - builder.AddComponentParameter(1, "ChildContent", this.MessageListContext.GetTemplate(message)); + builder.AddComponentParameter(5, "ChildContent", this.MessageListContext.GetTemplate(message)); builder.CloseComponent(); } + + // Close container div + builder.CloseElement(); } /// diff --git a/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/Demos/AgenticChat/AgenticChatDemo.razor.css b/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/Demos/AgenticChat/AgenticChatDemo.razor.css index 85196f9504..6b379d13f3 100644 --- a/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/Demos/AgenticChat/AgenticChatDemo.razor.css +++ b/dotnet/samples/AGUIDojo/AGUIDojoClient/Components/Demos/AgenticChat/AgenticChatDemo.razor.css @@ -55,35 +55,12 @@ } .chat-input-container { - padding: 1.5rem 2rem 2rem; + padding: 1rem 2rem 1.5rem; border-top: 1px solid #e5e5e5; background: white; } -::deep .agent-suggestions { - display: flex; - flex-wrap: wrap; - gap: 0.5rem; - justify-content: flex-start; - margin-bottom: 0.75rem; -} -::deep .suggestion-button { - padding: 0.5rem 1rem; - background: white; - border: 1px solid #d1d1d1; - border-radius: 1rem; - cursor: pointer; - font-size: 0.875rem; - color: #424242; - transition: all 0.2s; -} - -::deep .suggestion-button:hover { - background: #f0f0f0; - border-color: #0078d4; - color: #0078d4; -} .agentic-chat-demo { height: 100%; @@ -92,6 +69,6 @@ } .chat-container { - padding: 20px; + padding: 0 2rem 1.5rem; border-top: 1px solid #e0e0e0; } diff --git a/dotnet/samples/AGUIDojo/AGUIDojoClient/wwwroot/app.css b/dotnet/samples/AGUIDojo/AGUIDojoClient/wwwroot/app.css index d691fdbf82..59b961673b 100644 --- a/dotnet/samples/AGUIDojo/AGUIDojoClient/wwwroot/app.css +++ b/dotnet/samples/AGUIDojo/AGUIDojoClient/wwwroot/app.css @@ -92,7 +92,6 @@ h1:focus { align-items: flex-end; gap: 12px; width: 100%; - padding: 16px; background-color: #fff; } @@ -200,7 +199,6 @@ h1:focus { .agent-loading-indicator { display: flex; align-items: flex-start; - padding: 12px 16px; } .agent-loading-indicator .agent-loading-dots { @@ -222,7 +220,7 @@ h1:focus { display: flex; flex-wrap: wrap; gap: 8px; - padding: 8px 16px; + margin-bottom: 12px; } .agent-suggestions .suggestion-button { @@ -267,3 +265,45 @@ h1:focus { border-top: 1px solid #e5e7eb; background-color: #fff; } + +/* Chat message styles */ +.messages-container { + display: flex; + flex-direction: column; + gap: 12px; +} + +.chat-message { + display: flex; + flex-direction: column; + max-width: 80%; + padding: 12px 16px; + border-radius: 8px; + border: 1px solid #e5e7eb; + line-height: 1.5; +} + +/* User messages - right aligned with blue background */ +.chat-message.user-message { + align-self: flex-end; + background-color: #2563eb; + color: white; + border-color: #2563eb; +} + +/* Assistant messages - left aligned with light gray background */ +.chat-message.assistant-message { + align-self: flex-start; + background-color: #f3f4f6; + color: #1f2937; + border-color: #e5e7eb; +} + +/* Tool messages - left aligned, styled similar to assistant */ +.chat-message.tool-message { + align-self: flex-start; + background-color: #fef3c7; + color: #92400e; + border-color: #fcd34d; + font-size: 0.875rem; +}