File: Pipeline\TextBlockHandler.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;
 
// Fallback handler: claims TextContent and accumulates it into a single block for the
// duration of a model message. It runs last so more specific handlers get first refusal.
internal sealed class TextBlockHandler : ContentBlockHandler<RichContentBlock>
{
    public override BlockMappingResult<RichContentBlock> Handle(
        BlockMappingContext context, RichContentBlock state)
    {
        TextContent? textContent = null;
        foreach (var content in context.UnhandledContents)
        {
            if (content is TextContent tc)
            {
                textContent = tc;
                break;
            }
        }
 
        if (textContent is null)
        {
            if (state.Id != string.Empty)
            {
                return BlockMappingResult<RichContentBlock>.Complete();
            }
 
            return BlockMappingResult<RichContentBlock>.Pass();
        }
 
        context.MarkHandled(textContent);
        state.AppendText(textContent.Text ?? string.Empty);
        RebuildParagraphs(state);
 
        if (state.Id == string.Empty)
        {
            state.Id = context.Update.MessageId ?? Guid.NewGuid().ToString("N");
            return BlockMappingResult<RichContentBlock>.Emit(state, state);
        }
 
        return BlockMappingResult<RichContentBlock>.Update(state);
    }
 
    internal static void RebuildParagraphs(RichContentBlock state)
    {
        var rawText = state.RawText;
        if (rawText.Length == 0)
        {
            state.Paragraphs = Array.Empty<string>();
            return;
        }
 
        var paragraphs = new List<string>();
        var start = 0;
        while (start < rawText.Length)
        {
            var breakIndex = rawText.IndexOf("\n\n", start, StringComparison.Ordinal);
            if (breakIndex < 0)
            {
                AddParagraph(paragraphs, rawText.AsSpan(start));
                break;
            }
 
            if (breakIndex > start)
            {
                AddParagraph(paragraphs, rawText.AsSpan(start, breakIndex - start));
            }
 
            start = breakIndex + 2;
        }
 
        state.Paragraphs = paragraphs;
    }
 
    private static void AddParagraph(List<string> paragraphs, ReadOnlySpan<char> text)
    {
        var trimmed = text.TrimEnd("\r\n".AsSpan());
        if (trimmed.Length == 0)
        {
            return;
        }
 
        paragraphs.Add(trimmed.ToString());
    }
}