| 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(); } } }