namespace AIStudio.Assistants.BeyondFactsLab; public partial class BeyondFactsLabAssistant { /// /// The forms of the preparation shown, by their steps. A step missing here has a form this version cannot open. /// private Dictionary forms = []; /// /// The work states of the preparation shown, by their steps. A step missing here has a work state this version /// cannot open. /// private Dictionary workStates = []; /// /// The step shown. /// private BflStep activeStep = BflStep.FRAMEWORK; /// /// Gets the position of the step shown in the stepper, which lists the steps in the order of their values. /// private int ActiveStepIndex => (int)this.activeStep; /// /// Shows a preparation with its forms, at the step the person looked at last. /// /// The preparation, or null to show none. private async Task ShowProjectAsync(BflProjectEntry? project) { this.selectedProject = project; this.ResolveProvider(project?.Project); if (project?.Project is { } openedProject) { this.forms = new(await this.Store.LoadFormsAsync(openedProject.ProjectId)); this.workStates = new(await this.Store.LoadWorkStatesAsync(openedProject.ProjectId)); this.activeStep = openedProject.CurrentStep; } else { this.forms = []; this.workStates = []; this.activeStep = BflStep.FRAMEWORK; } } /// /// Shows the new time of change of a preparation after a work state or a result of it was stored. /// /// The preparation with its new time of change. private Task ProjectStoredAsync(BflProject project) { this.ReplaceProject(project); return Task.CompletedTask; } /// /// Reads the work states of the preparation shown again once the generator started, ended, or stored a draft. /// /// The step whose work state changed. private Task OnWorkStateChangedAsync(BflStepThreadLocation location) { if (this.selectedProject?.ProjectId != location.ProjectId) return Task.CompletedTask; return this.InvokeAsync(async () => { this.workStates = new(await this.Store.LoadWorkStatesAsync(location.ProjectId)); if (await this.Store.LoadAsync(location.ProjectId) is { } project) this.ReplaceProject(project); this.StateHasChanged(); }); } /// /// Shows the step the person chose in the stepper, and remembers it for the next time. /// /// The position of the step in the stepper. private async Task SelectStepAsync(int index) { var steps = BflStep.All; if (index < 0 || index >= steps.Count || steps[index] == this.activeStep) return; this.activeStep = steps[index]; this.scrollToActiveStep = true; if (this.selectedProject?.Project is not { } project) return; try { this.ReplaceProject(await this.Store.SetCurrentStepAsync(project.ProjectId, this.activeStep)); } catch (Exception exception) when (exception is IOException or UnauthorizedAccessException or InvalidDataException) { // Only the step to open next time is lost, which is not worth bothering the person with: this.Logger.LogWarning(exception, "Could not remember the step shown last in the BeyondFacts Lab."); } } /// /// Stores the form of a step after the person changed a value. /// /// /// Unlike the other changes, saving leaves the page usable: the fields stay enabled, so that the next field /// keeps the focus while the previous one is stored. /// /// The step. private async Task SaveFormAsync(BflStep step) { if (this.selectedProject?.Project is not { } project || !this.forms.TryGetValue(step, out var form)) return; try { this.ReplaceProject(await this.Store.SaveFormAsync(project.ProjectId, step, form)); if (BflMissingDetails.StepsOf(this.forms).Count > 0) this.projectsWithMissingDetails.Add(project.ProjectId); else this.projectsWithMissingDetails.Remove(project.ProjectId); } catch (Exception exception) when (exception is IOException or UnauthorizedAccessException or InvalidDataException) { this.Logger.LogError(exception, "Could not save the details of a step in the BeyondFacts Lab."); await this.MessageBus.SendError(new(Icons.Material.Filled.RecordVoiceOver, TA("AI Studio could not save your details. Please try again."))); } } /// /// Puts a preparation the store just wrote into the list, without reading the list again. /// /// The preparation. private void ReplaceProject(BflProject project) { var entry = BflProjectEntry.FromProject(project); this.projects = [.. this.projects.Select(candidate => candidate.ProjectId == project.ProjectId ? entry : candidate).OrderByDescending(candidate => candidate.ModifiedAtUtc)]; if (this.selectedProject?.ProjectId == project.ProjectId) this.selectedProject = entry; } /// /// Gets the line below the title of a step in the stepper. /// /// The step. private string StepSecondaryText(BflStep step) { if (!this.forms.TryGetValue(step, out var form)) return T("Cannot be opened"); var notes = new List(2); if (BflMissingDetails.Of(step, form).Count > 0) notes.Add(T("Details missing")); switch (BflStaleness.Of(step, this.workStates, this.forms)) { case BflWorkStateStatus.OUTDATED: notes.Add(T("Outdated")); break; case BflWorkStateStatus.DETAILS_CHANGED: notes.Add(T("Details changed")); break; } return string.Join(" ยท ", notes); } /// /// Tells whether the stepper shows a step as done: the person confirmed its work state, and no earlier step changed /// since. /// /// The step. private bool IsStepCompleted(BflStep step) => BflStaleness.Of(step, this.workStates, this.forms) is BflWorkStateStatus.CONFIRMED or BflWorkStateStatus.DETAILS_CHANGED; }