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