Fixed build progress title

This commit is contained in:
Thorsten Sommer 2026-07-31 21:21:53 +02:00
parent d1ab57cb60
commit d3e53fe5fe
Signed by untrusted user who does not match committer: tsommer
GPG Key ID: 371BBA77A02C0108
2 changed files with 84 additions and 17 deletions

View File

@ -57,15 +57,24 @@ public partial class VisualBriefingAssistant
/// <summary>
/// Gets the localized collapsed build-progress summary.
/// </summary>
private string BuildProgressTitle => this.latestBuild?.Status switch
private string BuildProgressTitle
{
VisualBriefingBuildStatus.COMPLETED => $"{T("Build progress")} · {T("Completed")}",
VisualBriefingBuildStatus.FAILED => $"{T("Build progress")} · {T("Failed")}",
VisualBriefingBuildStatus.CANCELED => $"{T("Build progress")} · {T("Canceled")}",
VisualBriefingBuildStatus.AWAITING_REBUILD => $"{T("Build progress")} · {T("Action required")}",
get
{
var title = this.latestBuild?.Status switch
{
VisualBriefingBuildStatus.COMPLETED => $"{T("Build progress")} · {T("Completed")}",
VisualBriefingBuildStatus.FAILED => $"{T("Build progress")} · {T("Failed")}",
VisualBriefingBuildStatus.CANCELED => $"{T("Build progress")} · {T("Canceled")}",
VisualBriefingBuildStatus.AWAITING_REBUILD => $"{T("Build progress")} · {T("Action required")}",
_ => $"{T("Build progress")} · {T("Running")}",
};
_ => $"{T("Build progress")} · {T("Running")}",
};
var duration = this.CalculateBuildDuration(this.latestBuild?.Stages ?? []);
return duration > TimeSpan.Zero ? $"{title} · {FormatBuildDuration(duration)}" : title;
}
}
/// <summary>
/// Keeps the status stepper informational while allowing actions inside the active step.
@ -311,8 +320,38 @@ public partial class VisualBriefingAssistant
if (this.selectedBriefing?.BriefingId != briefingId)
return;
this.latestBuild = this.BuildProgressService.GetLatest(briefingId);
_ = this.InvokeAsync(this.StateHasChanged);
_ = this.InvokeAsync(() =>
{
if (this.selectedBriefing?.BriefingId != briefingId)
return;
this.latestBuild = this.BuildProgressService.GetLatest(briefingId);
this.buildDurationReferenceUtc = DateTimeOffset.UtcNow;
this.StateHasChanged();
});
}
/// <summary>
/// Refreshes live build durations at most once per second while a selected stage is running.
/// </summary>
private async Task MonitorBuildDurationAsync(CancellationToken token)
{
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(1));
try
{
while (await timer.WaitForNextTickAsync(token))
await this.InvokeAsync(() =>
{
if (this.latestBuild?.Stages.Any(stage => stage.Status is VisualBriefingBuildStageStatus.RUNNING) != true)
return;
this.buildDurationReferenceUtc = DateTimeOffset.UtcNow;
this.StateHasChanged();
});
}
catch (OperationCanceledException) when (token.IsCancellationRequested)
{
}
}
/// <summary>
@ -408,16 +447,35 @@ public partial class VisualBriefingAssistant
? T("Completed")
: T("Not started");
var duration = records
.Where(record => record.StartedAtUtc is not null)
.Aggregate(TimeSpan.Zero, (total, record) =>
total + ((record.FinishedAtUtc ?? DateTimeOffset.UtcNow) - record.StartedAtUtc!.Value));
return duration > TimeSpan.Zero
? $"{status} · {duration.TotalSeconds:0.0} s"
: status;
var duration = this.CalculateBuildDuration(records);
return duration > TimeSpan.Zero ? $"{status} · {FormatBuildDuration(duration)}" : status;
}
/// <summary>
/// Calculates active processing time without counting reused stages or time between resume attempts.
/// </summary>
private TimeSpan CalculateBuildDuration(IEnumerable<VisualBriefingBuildStageRecord> records) => records
.Where(record => record.StartedAtUtc is not null && record.Status is not VisualBriefingBuildStageStatus.SKIPPED)
.Aggregate(TimeSpan.Zero, (total, record) => total + this.CalculateStageDuration(record));
/// <summary>
/// Calculates one stage duration against the shared live timestamp.
/// </summary>
private TimeSpan CalculateStageDuration(VisualBriefingBuildStageRecord record)
{
var finishedAtUtc = record.Status is VisualBriefingBuildStageStatus.RUNNING ? this.buildDurationReferenceUtc : record.FinishedAtUtc;
if (record.StartedAtUtc is null || finishedAtUtc is null)
return TimeSpan.Zero;
var duration = finishedAtUtc.Value - record.StartedAtUtc.Value;
return duration > TimeSpan.Zero ? duration : TimeSpan.Zero;
}
/// <summary>
/// Formats a build duration using the current UI culture.
/// </summary>
private static string FormatBuildDuration(TimeSpan duration) => $"{duration.TotalSeconds:0.0} s";
/// <summary>
/// Gets the safe failure reason for a UI group.
/// </summary>

View File

@ -91,6 +91,9 @@ public partial class VisualBriefingAssistant : MSGComponentBase
/// <summary>Stops the background source-status monitor.</summary>
private readonly CancellationTokenSource sourceMonitorCancellation = new();
/// <summary>Stops the live build-duration monitor.</summary>
private readonly CancellationTokenSource buildDurationMonitorCancellation = new();
/// <summary>Stores available and recoverable projects ordered by most recent modification.</summary>
private IReadOnlyList<VisualBriefingProjectEntry> projects = [];
@ -169,6 +172,9 @@ public partial class VisualBriefingAssistant : MSGComponentBase
/// <summary>Stores the latest persistent or live build shown in the stepper.</summary>
private VisualBriefingBuildRecord? latestBuild;
/// <summary>Stores the shared timestamp used to render consistent live build durations.</summary>
private DateTimeOffset buildDurationReferenceUtc = DateTimeOffset.UtcNow;
/// <summary>Stores incompatible validated content offered for rebuild continuation.</summary>
private Guid? reusableContentBuildId;
@ -199,6 +205,7 @@ public partial class VisualBriefingAssistant : MSGComponentBase
this.BuildProgressService.Changed += this.BuildProgressChanged;
await this.ReloadListAsync();
_ = this.MonitorSourceStatusAsync(this.sourceMonitorCancellation.Token);
_ = this.MonitorBuildDurationAsync(this.buildDurationMonitorCancellation.Token);
var deferredInstruction = this.MessageBus.CheckDeferredMessages<string>(Event.SEND_TO_VISUAL_BRIEFING_ASSISTANT).FirstOrDefault();
if (!string.IsNullOrWhiteSpace(deferredInstruction))
@ -220,6 +227,8 @@ public partial class VisualBriefingAssistant : MSGComponentBase
{
this.sourceMonitorCancellation.Cancel();
this.sourceMonitorCancellation.Dispose();
this.buildDurationMonitorCancellation.Cancel();
this.buildDurationMonitorCancellation.Dispose();
this.MediaTranscriptionService.StateChanged -= this.MediaStateChanged;
this.BuildProgressService.Changed -= this.BuildProgressChanged;
base.DisposeResources();