File: Components\MessageListContext.cs
Web Access
Project: src\aspnetcore\src\Components\AI\src\Microsoft.AspNetCore.Components.AI.csproj (Microsoft.AspNetCore.Components.AI)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Microsoft.Extensions.AI;
 
namespace Microsoft.AspNetCore.Components.AI;
 
/// <summary>
/// Resolves the markup used to render a block inside a <see cref="MessageList"/>. Renderers
/// registered with <see cref="BlockRenderer{TBlock}"/> take precedence over the built-in
/// rendering.
/// </summary>
public class MessageListContext
{
    private readonly List<BlockRendererRegistration> _registrations = new();
 
    /// <summary>
    /// Returns the markup for a block.
    /// </summary>
    /// <param name="block">The block to render.</param>
    /// <returns>The markup that renders <paramref name="block"/>.</returns>
    public RenderFragment RenderBlock(ContentBlock block)
    {
        ArgumentNullException.ThrowIfNull(block);
 
        for (var i = _registrations.Count - 1; i >= 0; i--)
        {
            var reg = _registrations[i];
            if (reg.BlockType.IsAssignableFrom(block.GetType())
                && (reg.When is null || reg.When(block)))
            {
                return reg.Render(block);
            }
        }
 
        return builder =>
        {
            if (block is RichContentBlock rich)
            {
                var role = block.Role == ChatRole.User ? "user" : "assistant";
                builder.OpenElement(0, "div");
                builder.AddAttribute(1, "class", $"sc-ai-message sc-ai-message--{role}");
                builder.OpenElement(2, "div");
                builder.AddAttribute(3, "class", "sc-ai-message__bubble");
                builder.OpenElement(4, "div");
                var contentClass = block.LifecycleState == BlockLifecycleState.Active
                    ? "sc-ai-message__content sc-ai-message__content--streaming"
                    : "sc-ai-message__content";
                builder.AddAttribute(5, "class", contentClass);
                builder.AddContent(6, rich.RawText);
                builder.CloseElement(); // content div
                builder.CloseElement(); // bubble div
                builder.CloseElement(); // message div
            }
            else
            {
                builder.OpenElement(0, "div");
                builder.AddAttribute(1, "class", "sc-ai-unknown-block");
                builder.AddContent(2, block.GetType().Name);
                builder.CloseElement();
            }
        };
    }
 
    internal Action? OnRegistrationsChanged { get; set; }
 
    internal void AddRegistration(BlockRendererRegistration registration)
    {
        _registrations.Add(registration);
        OnRegistrationsChanged?.Invoke();
    }
 
    internal void RemoveRegistration(BlockRendererRegistration registration)
    {
        _registrations.Remove(registration);
        OnRegistrationsChanged?.Invoke();
    }
}