using AIStudio.Dialogs;
using AIStudio.Tools.Rust;
using DialogOptions = AIStudio.Dialogs.DialogOptions;
namespace AIStudio.Assistants.VisualBriefing;
public partial class VisualBriefingAssistant
{
///
/// Gets whether the selected revision references all four intermediate artifacts.
///
private bool SelectedVersionSupportsEdits => this.VersionSupportsSemanticEdits(this.selectedRevisionId);
///
/// Gets whether one revision references the complete semantic artifact set.
///
/// The revision to inspect.
/// Whether the revision can be edited or recompiled without rebuilding its inputs.
private bool VersionSupportsSemanticEdits(Guid revisionId) =>
this.selectedBriefing?.Versions.FirstOrDefault(version =>
version.RevisionId == revisionId) is
{
SchemaVersion: VisualBriefingVersions.SCHEMA,
IntermediateArtifactVersion: VisualBriefingVersions.INTERMEDIATE_ARTIFACT,
EvidenceContractVersion: VisualBriefingVersions.EVIDENCE_CONTRACT,
PlanContractVersion: VisualBriefingVersions.PLAN_CONTRACT,
ContentContractVersion: VisualBriefingVersions.CONTENT_CONTRACT,
DesignContractVersion: VisualBriefingVersions.DESIGN_CONTRACT,
EvidenceArtifactId: not null,
PlanArtifactId: not null,
ContentArtifactId: not null,
PresentationArtifactId: not null,
};
///
/// Defines CanGoBackward for the visual briefing feature.
///
private bool CanGoBackward => this.GetSelectedVersionIndex() > 0;
///
/// Gets whether a newer immutable revision can be selected.
///
private bool CanGoForward
{
get
{
var index = this.GetSelectedVersionIndex();
return index >= 0 && index < (this.selectedBriefing?.Versions.Count ?? 0) - 1;
}
}
///
/// Defines PreviewContainerClass for the visual briefing feature.
///
private string PreviewContainerClass => $"visual-briefing-preview visual-briefing-preview-{this.previewDevice.ToString().ToLowerInvariant()}";
///
/// Defines SelectRevisionAsync for the visual briefing feature.
///
private Task SelectRevisionAsync(Guid revisionId)
{
if (this.selectedBriefing is null ||
this.selectedBriefing.Versions.All(version => version.RevisionId != revisionId))
return Task.CompletedTask;
this.selectedRevisionId = revisionId;
var token = this.PreviewTokenService.Issue(this.selectedBriefing.BriefingId, revisionId);
this.previewUrl = $"/visual-briefing/preview/{this.selectedBriefing.BriefingId:D}/{revisionId:D}?token={Uri.EscapeDataString(token)}";
return Task.CompletedTask;
}
///
/// Defines PreviousVersionAsync for the visual briefing feature.
///
private async Task PreviousVersionAsync()
{
var versions = this.OrderedVersions();
var index = this.GetSelectedVersionIndex();
if (index > 0)
await this.SelectRevisionAsync(versions[index - 1].RevisionId);
}
///
/// Defines NextVersionAsync for the visual briefing feature.
///
private async Task NextVersionAsync()
{
var versions = this.OrderedVersions();
var index = this.GetSelectedVersionIndex();
if (index >= 0 && index < versions.Count - 1)
await this.SelectRevisionAsync(versions[index + 1].RevisionId);
}
///
/// Defines ExportAsync for the visual briefing feature.
///
private async Task ExportAsync()
{
if (this.selectedBriefing is null || this.selectedRevisionId == Guid.Empty)
return;
var sourcePath = await this.Store.GetVersionPathAsync(this.selectedBriefing.BriefingId, this.selectedRevisionId);
if (sourcePath is null)
return;
if (!await this.ConfirmLargeFileAsync(sourcePath, T("export")))
return;
var response = await this.RustService.SaveFile(
T("Export visual briefing"),
[FileTypes.VISUAL_BRIEFING_HTML],
$"{SafeFileName(this.editor.Name)}.html");
if (response.UserCancelled)
return;
if (PathComparer().Equals(Path.GetFullPath(sourcePath), Path.GetFullPath(response.SaveFilePath)))
{
await this.MessageBus.SendError(new(Icons.Material.Filled.SaveAs, T("Choose a different export location so the immutable briefing version is not overwritten.")));
return;
}
var verified = await this.Store.OpenIntegrityCheckedVersionAsync(this.selectedBriefing.BriefingId, this.selectedRevisionId);
if (verified is null)
{
await this.MessageBus.SendError(new(Icons.Material.Filled.GppBad, T("The selected briefing version failed its integrity check and cannot be exported.")));
return;
}
await using var source = verified.Value.Stream;
await using var destination = new FileStream(response.SaveFilePath, FileMode.Create, FileAccess.Write, FileShare.None, 65_536, true);
await source.CopyToAsync(destination);
var exportedVersion = this.selectedBriefing.Versions.First(version =>
version.RevisionId == this.selectedRevisionId);
this.Logger.LogInformation(
new EventId((int)VisualBriefingLogEventId.EXPORT, VisualBriefingLogEventId.EXPORT.ToString()),
"Visual briefing version exported. OperationId={OperationId} BuildId={BuildId} BriefingId={BriefingId} RevisionId={RevisionId} DocumentHash={DocumentHash} Bytes={Bytes}",
exportedVersion.OperationId,
exportedVersion.BuildId,
this.selectedBriefing.BriefingId,
exportedVersion.RevisionId,
exportedVersion.DocumentHash,
source.Length);
await this.MessageBus.SendSuccess(new(Icons.Material.Filled.FileDownload, T("The visual briefing was exported.")));
}
///
/// Defines ImportAsync for the visual briefing feature.
///
private async Task ImportAsync()
{
var response = await this.RustService.SelectFile(T("Import visual briefing"), [FileTypes.VISUAL_BRIEFING_HTML]);
if (response.UserCancelled || !await this.ConfirmLargeFileAsync(response.SelectedFilePath, T("import")))
return;
var imported = await this.Store.ImportAsync(response.SelectedFilePath, importNameConflictAsCopy: false);
if (imported.RequiresCopyConfirmation)
{
var parameters = new DialogParameters
{
{ dialog => dialog.Message, T("This briefing ID already exists under another name. Import it as a copy with a new ID?") },
};
var reference = await this.DialogService.ShowAsync(T("Import as copy"), parameters, DialogOptions.FULLSCREEN);
var result = await reference.Result;
if (result is null || result.Canceled)
return;
imported = await this.Store.ImportAsync(response.SelectedFilePath, importNameConflictAsCopy: true);
}
if (!imported.Success)
{
await this.MessageBus.SendError(new(Icons.Material.Filled.FileUpload, imported.Issue));
return;
}
await this.ReloadListAsync(imported.BriefingId);
await this.SelectRevisionAsync(imported.RevisionId);
this.Logger.LogInformation(
new EventId((int)VisualBriefingLogEventId.IMPORT, VisualBriefingLogEventId.IMPORT.ToString()),
"Visual briefing version imported. BriefingId={BriefingId} RevisionId={RevisionId} Deduplicated={Deduplicated}",
imported.BriefingId,
imported.RevisionId,
imported.WasDeduplicated);
await this.MessageBus.SendSuccess(new(Icons.Material.Filled.FileUpload, imported.WasDeduplicated ? T("This briefing revision was already imported.") : T("The visual briefing was imported.")));
}
///
/// Defines OrderedVersions for the visual briefing feature.
///
private IReadOnlyList OrderedVersions() =>
this.selectedBriefing?.Versions.OrderBy(version => version.VersionNumber).ToArray() ?? [];
///
/// Defines GetSelectedVersionIndex for the visual briefing feature.
///
private int GetSelectedVersionIndex()
{
var versions = this.OrderedVersions();
for (var index = 0; index < versions.Count; index++)
if (versions[index].RevisionId == this.selectedRevisionId)
return index;
return -1;
}
///
/// Defines SafeFileName for the visual briefing feature.
///
private static string SafeFileName(string value)
{
var invalid = Path.GetInvalidFileNameChars().ToHashSet();
var name = new string(value.Select(character => invalid.Contains(character) ? '-' : character).ToArray()).Trim();
return string.IsNullOrWhiteSpace(name) ? "visual-briefing" : name;
}
}