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;
+}