using AIStudio.Components; using AIStudio.Dialogs; using AIStudio.Dialogs.Settings; using AIStudio.Tools.AssistantSessions; using AIStudio.Tools.Services; using Microsoft.AspNetCore.Components; using DialogOptions = AIStudio.Dialogs.DialogOptions; using ComponentKind = AIStudio.Tools.Components; namespace AIStudio.Assistants.VisualBriefing; /// /// Defines VisualBriefingAssistant for the visual briefing feature. /// public partial class VisualBriefingAssistant : MSGComponentBase { /// /// Defines Store for the visual briefing feature. /// [Inject] private VisualBriefingStore Store { get; init; } = null!; /// /// Defines BuildOrchestrator for the visual briefing feature. /// [Inject] private VisualBriefingBuildOrchestrator BuildOrchestrator { get; init; } = null!; /// /// Defines BuildProgressService for the visual briefing feature. /// [Inject] private VisualBriefingBuildProgressService BuildProgressService { get; init; } = null!; /// /// Defines PreviewTokenService for the visual briefing feature. /// [Inject] private VisualBriefingPreviewTokenService PreviewTokenService { get; init; } = null!; /// /// Defines RustService for the visual briefing feature. /// [Inject] private RustService RustService { get; init; } = null!; /// /// Defines MediaTranscriptionService for the visual briefing feature. /// [Inject] private MediaTranscriptionService MediaTranscriptionService { get; init; } = null!; /// /// Defines DialogService for the visual briefing feature. /// [Inject] private IDialogService DialogService { get; init; } = null!; /// /// Defines AssistantSessionService for the visual briefing feature. /// [Inject] private AssistantSessionService AssistantSessionService { get; init; } = null!; /// /// Defines NavigationManager for the visual briefing feature. /// [Inject] private NavigationManager NavigationManager { get; init; } = null!; /// /// Defines Logger for the visual briefing feature. /// [Inject] private ILogger Logger { get; init; } = null!; /// Tracks briefing projects with an active generation. private readonly HashSet generatingBriefings = []; /// Stops the background source-status monitor. private readonly CancellationTokenSource sourceMonitorCancellation = new(); /// Stores available and recoverable projects ordered by most recent modification. private IReadOnlyList projects = []; /// Stores the project entry currently selected in the list. private VisualBriefingProjectEntry? selectedProject; /// Stores the project currently displayed by the editor. private VisualBriefingManifest? selectedBriefing; /// Stores every editable value of the selected briefing. private VisualBriefingEditorState editor = new(); /// Stores the selected immutable revision. private Guid selectedRevisionId; /// Stores the preview viewport preset. private VisualBriefingPreviewDevice previewDevice = VisualBriefingPreviewDevice.DESKTOP; /// Stores the current tokenized preview URL. private string previewUrl = string.Empty; /// Stores the last auto-saved UI fingerprint. private string lastPersistedState = string.Empty; /// Stores clipboard-safe diagnostics for the latest operation. private VisualBriefingOperationDiagnostics? lastBuildDiagnostics; /// Stores the latest persistent or live build shown in the stepper. private VisualBriefingBuildRecord? latestBuild; /// Stores incompatible validated content offered for rebuild continuation. private Guid? reusableContentBuildId; /// Owns MudBlazor validation for the selected briefing editor. private MudForm? visualBriefingForm; /// Stores the current MudBlazor validation messages. private string[] formIssues = []; /// Requests validation after conditional form controls have rendered. private bool formValidationPending; /// Stores whether this component instance has already left the renderer. private bool isDisposed; /// Carries the spellchecking configuration to every text input of this assistant. private static readonly Dictionary USER_INPUT_ATTRIBUTES = new(); /// /// Defines IsCurrentBusy for the visual briefing feature. /// private bool IsCurrentBusy => this.selectedBriefing is not null && (this.IsGenerating(this.selectedBriefing.BriefingId) || this.MediaTranscriptionService.IsBusy(this.CurrentMediaOwner)); /// /// Defines OnInitializedAsync for the visual briefing feature. /// protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); if (!this.SettingsManager.IsAssistantVisible( ComponentKind.VISUAL_BRIEFING_ASSISTANT, assistantName: T("Visual Briefing Assistant"), requiredPreviewFeature: ComponentKind.VISUAL_BRIEFING_ASSISTANT.RequiredPreviewFeature())) { this.NavigationManager.NavigateTo(Routes.ASSISTANTS); return; } this.ApplyFilters([], [Event.SEND_TO_VISUAL_BRIEFING_ASSISTANT, Event.CONFIGURATION_CHANGED]); this.MediaTranscriptionService.StateChanged += this.MediaStateChanged; this.BuildProgressService.Changed += this.BuildProgressChanged; await this.ReloadListAsync(); await this.ConsumePendingMediaOutcomesAsync(); _ = this.MonitorSourceStatusAsync(this.sourceMonitorCancellation.Token); var deferredInstruction = this.MessageBus.CheckDeferredMessages(Event.SEND_TO_VISUAL_BRIEFING_ASSISTANT).FirstOrDefault(); if (!string.IsNullOrWhiteSpace(deferredInstruction)) { if (this.selectedBriefing is null) await this.CreateBriefingAsync(); this.editor.Instruction = deferredInstruction; await this.SaveCurrentAsync(); } await this.ResumeSelectedBuildAsync(); } /// /// Defines OnParametersSetAsync for the visual briefing feature. /// protected override async Task OnParametersSetAsync() { // Configure the spellchecking for the user input: this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES); await base.OnParametersSetAsync(); } /// /// Defines DisposeResources for the visual briefing feature. /// protected override void DisposeResources() { this.isDisposed = true; this.sourceMonitorCancellation.Cancel(); this.sourceMonitorCancellation.Dispose(); this.MediaTranscriptionService.StateChanged -= this.MediaStateChanged; this.BuildProgressService.Changed -= this.BuildProgressChanged; base.DisposeResources(); } /// /// Defines OnAfterRenderAsync for the visual briefing feature. /// protected override async Task OnAfterRenderAsync(bool firstRender) { await base.OnAfterRenderAsync(firstRender); if (this.formValidationPending && this.visualBriefingForm is not null) { this.formValidationPending = false; await this.visualBriefingForm.Validate(); } if (this.selectedBriefing is null || this.IsCurrentBusy) return; var currentState = this.BuildPersistenceFingerprint(); if (string.Equals(currentState, this.lastPersistedState, StringComparison.Ordinal)) return; this.lastPersistedState = currentState; try { await this.SaveCurrentAsync(); } catch (Exception exception) when (exception is IOException or UnauthorizedAccessException or InvalidDataException or InvalidOperationException) { this.lastPersistedState = string.Empty; this.Logger.LogWarning( "Could not auto-save visual briefing. BriefingId={BriefingId} ExceptionType={ExceptionType}", this.selectedBriefing.BriefingId, exception.GetType().Name); await this.MessageBus.SendError(new(Icons.Material.Filled.SaveAs, T("The visual briefing settings could not be saved."))); } } /// /// Defines T for the visual briefing feature. /// protected override async Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default { if (triggeredEvent is Event.SEND_TO_VISUAL_BRIEFING_ASSISTANT && data is string text) { if (this.selectedBriefing is null) await this.CreateBriefingAsync(); this.editor.Instruction = text; await this.SaveCurrentAsync(); this.StateHasChanged(); return; } if (triggeredEvent is Event.CONFIGURATION_CHANGED) { // The spellchecking setting might have changed. Since this page is not re-parameterized // while the user stays on it, we have to read the setting again here: this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES); this.StateHasChanged(); } await base.ProcessIncomingMessage(sendingComponent, triggeredEvent, data); } /// /// Defines ConfirmLargeFileAsync for the visual briefing feature. /// private async Task ConfirmLargeFileAsync(string path, string operation) { if (new FileInfo(path).Length < 50L * 1_024 * 1_024) return true; var parameters = new DialogParameters { { dialog => dialog.Message, string.Format(T("This briefing is larger than 50 MB. Continue with the {0}?"), operation) }, }; var reference = await this.DialogService.ShowAsync(T("Large visual briefing"), parameters, DialogOptions.FULLSCREEN); var result = await reference.Result; return result is not null && !result.Canceled; } /// /// Opens the visual briefing settings. /// /// /// Every assistant derived from offers this next to its /// title. This one has to wire it up itself, because it does not use that base component. /// private async Task OpenSettingsDialogAsync() => await this.DialogService.ShowAsync(null, new DialogParameters(), DialogOptions.FULLSCREEN); /// /// Defines PathComparer for the visual briefing feature. /// private static StringComparer PathComparer() => OperatingSystem.IsWindows() ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal; }