File: Pipeline\BlockMappingContext.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>
/// Carries a single model update through the block mapping pipeline and tracks which
/// parts of the update handlers have already claimed.
/// </summary>
public class BlockMappingContext
{
    private readonly bool[] _handled;
    private int _handledCount;
    private bool _updateHandled;
 
    internal BlockMappingContext(ChatResponseUpdate update)
    {
        Update = update;
        _handled = new bool[update.Contents.Count];
    }
 
    /// <summary>
    /// Gets the update being mapped.
    /// </summary>
    public ChatResponseUpdate Update { get; }
 
    /// <summary>
    /// Gets the contents of <see cref="Update"/> that no handler has claimed yet.
    /// </summary>
    public UnhandledContentsEnumerable UnhandledContents => new(Update.Contents, _handled);
 
    /// <summary>
    /// Marks a content item as claimed so that later handlers do not see it.
    /// </summary>
    /// <param name="content">The content item that was handled.</param>
    public void MarkHandled(AIContent content)
    {
        var contents = Update.Contents;
        for (var i = 0; i < contents.Count; i++)
        {
            if (ReferenceEquals(contents[i], content))
            {
                if (!_handled[i])
                {
                    _handled[i] = true;
                    _handledCount++;
                }
                return;
            }
        }
    }
 
    /// <summary>
    /// Marks the update itself as handled. Use this for updates that carry no content items.
    /// </summary>
    public void MarkUpdateHandled()
    {
        _updateHandled = true;
    }
 
    /// <summary>
    /// Gets a value indicating whether every part of the update has been claimed.
    /// </summary>
    public bool AllHandled =>
        _handledCount >= Update.Contents.Count && (Update.Contents.Count > 0 || _updateHandled);
 
    internal int HandledProgress => _handledCount + (_updateHandled ? 1 : 0);
 
    /// <summary>
    /// Enumerates the content items of an update that have not been claimed by a handler.
    /// </summary>
    public readonly struct UnhandledContentsEnumerable
    {
        private readonly IList<AIContent> _contents;
        private readonly bool[] _handled;
 
        internal UnhandledContentsEnumerable(IList<AIContent> contents, bool[] handled)
        {
            _contents = contents;
            _handled = handled;
        }
 
        /// <summary>
        /// Returns an enumerator over the unclaimed content items.
        /// </summary>
        /// <returns>An enumerator over the unclaimed content items.</returns>
        public UnhandledContentsEnumerator GetEnumerator() => new(_contents, _handled);
    }
 
    /// <summary>
    /// Enumerator over the content items of an update that have not been claimed by a handler.
    /// </summary>
    public struct UnhandledContentsEnumerator
    {
        private readonly IList<AIContent> _contents;
        private readonly bool[] _handled;
        private int _index;
 
        internal UnhandledContentsEnumerator(IList<AIContent> contents, bool[] handled)
        {
            _contents = contents;
            _handled = handled;
            _index = -1;
        }
 
        /// <summary>
        /// Gets the content item at the current position.
        /// </summary>
        public AIContent Current => _contents[_index];
 
        /// <summary>
        /// Advances to the next unclaimed content item.
        /// </summary>
        /// <returns><see langword="true"/> if another unclaimed item exists; otherwise <see langword="false"/>.</returns>
        public bool MoveNext()
        {
            while (++_index < _contents.Count)
            {
                if (!_handled[_index])
                {
                    return true;
                }
            }
            return false;
        }
    }
}