File: Model\Assistant\ChatViewModel.cs
Web Access
Project: src\src\Aspire.Dashboard\Aspire.Dashboard.csproj (Aspire.Dashboard)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Web;
using Aspire.Dashboard.Model.Markdown;
using Microsoft.Extensions.AI;
 
namespace Aspire.Dashboard.Model.Assistant;
 
public sealed class ChatViewModel
{
    private static long s_nextId;
 
    public ChatViewModel(bool isUserMessage)
    {
        Id = Interlocked.Increment(ref s_nextId);
        ElementId = $"chat-message-{Id}";
        IsUserMessage = isUserMessage;
    }
 
    private string? _markdown;
 
    public long Id { get; }
    public string ElementId { get; }
    public bool IsUserMessage { get; }
 
    public bool IsComplete { get; set; }
    public bool IsLiked { get; set; }
    public bool IsDisliked { get; set; }
    public string PromptText { get; set; } = string.Empty;
    public string Html { get; set; } = string.Empty;
    public string? ErrorMessage { get; set; }
    public bool IsForbidden { get; set; }
    public DateTime? LimitResetDate { get; set; }
 
    private readonly List<ChatMessageViewModelBase> _chatMessages = new();
 
    public int ChatMessageCount => _chatMessages.Count;
 
    public IEnumerable<ChatMessage> GetChatMessages()
    {
        foreach (var chatMessageViewModel in _chatMessages)
        {
            yield return chatMessageViewModel.GetChatMessage();
        }
    }
 
    public void AddChatMessage(ChatMessage chatMessage)
    {
        _chatMessages.Add(new StaticChatMessageViewModel(chatMessage));
    }
 
    public void AddChatMessage(Func<ChatMessage> chatMessageProvider)
    {
        _chatMessages.Add(new DynamicChatMessageViewModel(chatMessageProvider));
    }
 
    public void AppendMarkdown(string markdown, MarkdownProcessor markdownProcessor, bool inCompleteDocument = false, bool suppressSurroundingParagraph = false)
    {
        _markdown += markdown;
        Html = markdownProcessor.ToHtml(_markdown, inCompleteDocument: inCompleteDocument, suppressSurroundingParagraph: suppressSurroundingParagraph);
    }
 
    public void SetText(string text)
    {
        _markdown = null;
 
        // Prevent user injecting HTML into the page.
        var newText = HttpUtility.HtmlEncode(text);
 
        // New lines in message should be preserved in displayed text.
        newText = newText.Replace("\r\n", "<br />", StringComparison.Ordinal)
            .Replace("\r", "<br />", StringComparison.Ordinal)
            .Replace("\n", "<br />", StringComparison.Ordinal);
 
        Html = newText;
    }
 
    public void ClearMarkdown()
    {
        _markdown = null;
        Html = string.Empty;
    }
 
    private abstract class ChatMessageViewModelBase
    {
        public abstract ChatMessage GetChatMessage();
    }
 
    private sealed class DynamicChatMessageViewModel : ChatMessageViewModelBase
    {
        private readonly Func<ChatMessage> _chatMessageProvider;
 
        public DynamicChatMessageViewModel(Func<ChatMessage> chatMessageProvider)
        {
            _chatMessageProvider = chatMessageProvider;
        }
 
        public override ChatMessage GetChatMessage()
        {
            return _chatMessageProvider();
        }
    }
 
    private sealed class StaticChatMessageViewModel : ChatMessageViewModelBase
    {
        private readonly ChatMessage _chatMessage;
 
        public StaticChatMessageViewModel(ChatMessage chatMessage)
        {
            _chatMessage = chatMessage;
        }
 
        public override ChatMessage GetChatMessage()
        {
            return _chatMessage;
        }
    }
}