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,7 +57,11 @@ public partial class VisualBriefingAssistant
/// <summary> /// <summary>
/// Gets the localized collapsed build-progress summary. /// Gets the localized collapsed build-progress summary.
/// </summary> /// </summary>
private string BuildProgressTitle => this.latestBuild?.Status switch private string BuildProgressTitle
{
get
{
var title = this.latestBuild?.Status switch
{ {
VisualBriefingBuildStatus.COMPLETED => $"{T("Build progress")} · {T("Completed")}", VisualBriefingBuildStatus.COMPLETED => $"{T("Build progress")} · {T("Completed")}",
VisualBriefingBuildStatus.FAILED => $"{T("Build progress")} · {T("Failed")}", VisualBriefingBuildStatus.FAILED => $"{T("Build progress")} · {T("Failed")}",
@ -67,6 +71,11 @@ public partial class VisualBriefingAssistant
_ => $"{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> /// <summary>
/// Keeps the status stepper informational while allowing actions inside the active step. /// Keeps the status stepper informational while allowing actions inside the active step.
/// </summary> /// </summary>
@ -307,12 +316,42 @@ public partial class VisualBriefingAssistant
/// Applies a content-free live progress update for the selected project. /// Applies a content-free live progress update for the selected project.
/// </summary> /// </summary>
private void BuildProgressChanged(Guid briefingId) private void BuildProgressChanged(Guid briefingId)
{
if (this.selectedBriefing?.BriefingId != briefingId)
return;
_ = this.InvokeAsync(() =>
{ {
if (this.selectedBriefing?.BriefingId != briefingId) if (this.selectedBriefing?.BriefingId != briefingId)
return; return;
this.latestBuild = this.BuildProgressService.GetLatest(briefingId); this.latestBuild = this.BuildProgressService.GetLatest(briefingId);
_ = this.InvokeAsync(this.StateHasChanged); 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> /// <summary>
@ -408,16 +447,35 @@ public partial class VisualBriefingAssistant
? T("Completed") ? T("Completed")
: T("Not started"); : T("Not started");
var duration = records var duration = this.CalculateBuildDuration(records);
.Where(record => record.StartedAtUtc is not null) return duration > TimeSpan.Zero ? $"{status} · {FormatBuildDuration(duration)}" : status;
.Aggregate(TimeSpan.Zero, (total, record) =>
total + ((record.FinishedAtUtc ?? DateTimeOffset.UtcNow) - record.StartedAtUtc!.Value));
return duration > TimeSpan.Zero
? $"{status} · {duration.TotalSeconds:0.0} s"
: 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> /// <summary>
/// Gets the safe failure reason for a UI group. /// Gets the safe failure reason for a UI group.
/// </summary> /// </summary>

View File

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