// Copyright (c) Microsoft. All rights reserved. using Microsoft.Extensions.AI; namespace Microsoft.AspNetCore.Components.AI; #pragma warning disable CA1812 // Internal class is apparently never instantiated internal sealed class DefaultMessageTemplate : MessageTemplateBase #pragma warning restore CA1812 // Internal class is apparently never instantiated { public override bool When(MessageContext context) => true; // Buffer to convert response updates to chat messages. private readonly List _buffer = []; public DefaultMessageTemplate() { this.ChildContent = this.SelectTemplate; } private RenderFragment SelectTemplate(MessageContext messageContext) { 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, getRenderContents); } else { var getRenderContents = messageContext.RenderContents(); var updates = messageContext.ResponseUpdates ?? []; if (updates.Count == 0) { throw new InvalidOperationException("MessageContext must have either a ChatMessage or at least one ResponseUpdate."); } this._buffer.Clear(); this._buffer.AddMessages(updates); if (this._buffer.Count != 1) { throw new InvalidOperationException("DefaultMessageTemplate only supports a single ResponseUpdate."); } return CreateRenderMessage( this._buffer[0], getRenderContents); } } /// /// Checks if a message has any visible content that should be displayed. /// Messages that only contain FunctionResultContent are internal tool messages /// and should not be rendered as chat bubbles. /// FunctionCallContent is considered visible because content templates can render them /// (e.g., WeatherCallTemplate renders weather tool calls as cards). /// 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; } // FunctionCallContent is visible - content templates can render them // (e.g., WeatherCallTemplate renders weather tool calls as cards) if (content is FunctionCallContent) { return true; } // Other content types that are not function results are visible if (content is not FunctionResultContent) { return true; } } return false; } private static RenderFragment CreateRenderMessage( ChatMessage message, RenderFragment getRenderContents) { 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(message.MessageId)) { builder.AddAttribute(1, "id", message.MessageId); } builder.AddAttribute(2, "class", $"chat-message {roleClass}"); builder.AddContent(3, getRenderContents); builder.CloseElement(); }; } }