File: Components\Dialogs\AssistantModalDialog.razor.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 Aspire.Dashboard.Model.Assistant;
using Microsoft.AspNetCore.Components;
using Microsoft.FluentUI.AspNetCore.Components;
using Microsoft.JSInterop;
 
namespace Aspire.Dashboard.Components.Dialogs;
 
public partial class AssistantModalDialog : IAsyncDisposable
{
    [Parameter]
    public AssistantDialogViewModel Content { get; set; } = default!;
 
    [CascadingParameter]
    private FluentDialog Dialog { get; set; } = default!;
 
    [CascadingParameter]
    public required ViewportInformation ViewportInformation { get; set; }
 
    [Inject]
    public required IDialogService DialogService { get; init; }
 
    [Inject]
    public required NavigationManager NavigationManager { get; init; }
 
    [Inject]
    public required IJSRuntime JS { get; init; }
 
    [Inject]
    public required IAIContextProvider AIContextProvider { get; init; }
 
    [Inject]
    public required IServiceProvider ServiceProvider { get; init; }
 
    private readonly CancellationTokenSource _cts = new();
    private TaskCompletionSource? _disconnectTcs;
    private DotNetObjectReference<ChatModalInterop>? _chatModalInteropReference;
 
    protected override void OnInitialized()
    {
        InitializeChatViewModel();
        _chatModalInteropReference = DotNetObjectReference.Create(new ChatModalInterop(this));
    }
 
    private void InitializeChatViewModel()
    {
        _disconnectTcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
 
        Content.Chat.DisplayContainer = AssistantChatDisplayContainer.Dialog;
        Content.Chat.OnDisconnectCallback = () =>
        {
            _disconnectTcs?.TrySetResult();
            return Task.CompletedTask;
        };
    }
 
    protected override async Task OnParametersSetAsync()
    {
        if (ViewportInformation.IsDesktop && Content.OpenedForMobileView)
        {
            await CloseAndDisplaySidebarAsync();
        }
    }
 
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await JS.InvokeVoidAsync("attachChatClickEvent", AssistantChatViewModel.ChatAssistantContainerId, _chatModalInteropReference);
        }
    }
 
    public ValueTask DisposeAsync()
    {
        _cts.Cancel();
        _chatModalInteropReference?.Dispose();
 
        // If the assistant dialog was closed without switching to the sidebar, dispose the chat view model.
        if (Content.Chat.DisplayContainer == AssistantChatDisplayContainer.Dialog)
        {
            Content.Chat.Dispose();
        }
 
        return ValueTask.CompletedTask;
    }
 
    public static async Task OpenDialogAsync(IDialogService dialogService, string title, AssistantDialogViewModel viewModel)
    {
        var parameters = new DialogParameters
        {
            Id = "chat-modal-dialog",
            Title = title,
            Width = "min(800px, 100vw)",
            TrapFocus = true,
            Modal = true,
            PreventScroll = true
        };
 
        await dialogService.ShowDialogAsync<AssistantModalDialog>(viewModel, parameters);
    }
 
    private async Task CloseAndDisplaySidebarAsync()
    {
        Content.Chat.DisplayContainer = AssistantChatDisplayContainer.Switching;
        await CloseAndWaitForCleanUpAsync();
        await DisplaySidebarViewAsync();
    }
 
    private async Task CloseAndWaitForCleanUpAsync()
    {
        await Dialog.CloseAsync();
        await (_disconnectTcs?.Task ?? Task.CompletedTask);
    }
 
    private async Task DisplaySidebarViewAsync()
    {
        await AIContextProvider.LaunchAssistantSidebarAsync(Content.Chat);
    }
 
    public async Task StartNewChatAsync()
    {
        var viewModel = ServiceProvider.GetRequiredService<AssistantChatViewModel>();
        var initializeTask = viewModel.InitializeAsync();
 
        Content.Chat = viewModel;
        InitializeChatViewModel();
 
        await initializeTask;
    }
 
    private void OnModelInitialized()
    {
        StateHasChanged();
    }
 
    /// <summary>
    /// Handle user clicking on clicks in the browser.
    /// </summary>
    private sealed class ChatModalInterop
    {
        private readonly AssistantModalDialog _dialog;
 
        public ChatModalInterop(AssistantModalDialog dialog)
        {
            _dialog = dialog;
        }
 
        [JSInvokable]
        public async Task NavigateUrl(string url)
        {
            // Navigate to the URL and wait for the dialog to be closed
            _dialog.NavigationManager.NavigateTo(url);
            await _dialog.CloseAndDisplaySidebarAsync();
        }
    }
}