From 868a8b1cf39394408afa6bb49d744ceba0a6d26b Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Thu, 30 Jul 2026 21:52:09 +0200 Subject: [PATCH] Improved recompile logic to use AI Studio export pipeline --- .../VisualBriefingAssistant.razor | 2 +- .../VisualBriefingAssistant.razor.Build.cs | 11 +- ...sualBriefingBuildOrchestrator.Recompile.cs | 112 ++++++++++++------ .../VisualBriefingStore.Versions.cs | 3 +- 4 files changed, 84 insertions(+), 44 deletions(-) diff --git a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingAssistant.razor b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingAssistant.razor index d0c6b1ce..d1987622 100644 --- a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingAssistant.razor +++ b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingAssistant.razor @@ -182,7 +182,7 @@ @T("Recompile briefing") diff --git a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingAssistant.razor.Build.cs b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingAssistant.razor.Build.cs index 2414ead1..bee8af0f 100644 --- a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingAssistant.razor.Build.cs +++ b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingAssistant.razor.Build.cs @@ -193,7 +193,7 @@ public partial class VisualBriefingAssistant } /// - /// Recompiles the selected immutable revision with the current compiler and runtime. + /// Recompiles the selected immutable revision with the current AI Studio export pipeline. /// /// An optional parent used while resuming a persisted operation. private async Task RecompileAsync(Guid? parentRevisionOverride = null) @@ -234,13 +234,6 @@ public partial class VisualBriefingAssistant this.lastBuildDiagnostics = result.Diagnostics; this.latestBuild = this.BuildProgressService.GetLatest(briefingId) ?? (await this.Store.ListBuildsAsync(briefingId, cancellation.Token)).FirstOrDefault(); - if (result.FailureCode is VisualBriefingFailureCode.NO_CHANGES) - { - this.Snackbar.Add(T("The selected briefing version already uses the current compiler and runtime."), Severity.Info); - terminalStatus = AssistantSessionStatus.COMPLETED; - return; - } - if (!result.Success || result.Version is null) { terminalIssue = result.Issue; @@ -265,7 +258,7 @@ public partial class VisualBriefingAssistant ]; } - this.Snackbar.Add(T("The briefing was recompiled with the current AI Studio runtime."), Severity.Success); + this.Snackbar.Add(T("The briefing was recompiled with the current AI Studio version."), Severity.Success); terminalStatus = AssistantSessionStatus.COMPLETED; } catch (OperationCanceledException) diff --git a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingBuildOrchestrator.Recompile.cs b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingBuildOrchestrator.Recompile.cs index 530a743f..13e77d3f 100644 --- a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingBuildOrchestrator.Recompile.cs +++ b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingBuildOrchestrator.Recompile.cs @@ -5,8 +5,8 @@ namespace AIStudio.Assistants.VisualBriefing; internal sealed partial class VisualBriefingBuildOrchestrator { /// - /// Recompiles one immutable revision with the current deterministic compiler and standalone - /// runtime without accessing sources or calling a model. + /// Recompiles one immutable revision with the current deterministic export pipeline without + /// accessing sources or calling a model. /// /// The current local briefing manifest. /// The revision whose semantic artifacts are reused. @@ -144,6 +144,8 @@ internal sealed partial class VisualBriefingBuildOrchestrator compiled.TemplateHtml, compiled.Css, content.Charts.Count > 0)); + var contributions = await this.ResolveRecompileModelContributionsAsync(manifest.BriefingId, parentVersion, evidence, plan, content, previousPresentation, token); + var presentationModel = contributions.First(contribution => contribution.Role is VisualBriefingModelRole.DESIGN).Model; var presentation = new VisualBriefingPresentationArtifact { ArtifactId = Guid.NewGuid(), @@ -160,7 +162,7 @@ internal sealed partial class VisualBriefingBuildOrchestrator Css = compiled.Css, TemplateHash = compiled.TemplateHash, CssHash = compiled.CssHash, - Model = previousPresentation.Model, + Model = presentationModel, }; await this.store.WritePresentationArtifactAsync(manifest.BriefingId, presentation, token); @@ -204,7 +206,6 @@ internal sealed partial class VisualBriefingBuildOrchestrator await this.store.SaveBuildAsync(build, token); this.progressService.Publish(build); - var contributions = parentVersion.ModelContributions.ToList(); var revision = await this.store.AddRevisionAsync(new( manifest.BriefingId, parentRevisionId, @@ -230,35 +231,7 @@ internal sealed partial class VisualBriefingBuildOrchestrator var commitStage = GetStage(build, VisualBriefingBuildStage.COMMIT); if (!revision.Success || revision.Version is null) - { - if (revision.Issue.Contains("did not change", StringComparison.OrdinalIgnoreCase)) - { - assemblyStage.Status = VisualBriefingBuildStageStatus.COMPLETED; - assemblyStage.FinishedAtUtc = DateTimeOffset.UtcNow; - assemblyStage.OutputHash = parentVersion.PayloadHash; - MarkSkipped(build, VisualBriefingBuildStage.COMMIT, parentVersion.PayloadHash); - build.Status = VisualBriefingBuildStatus.COMPLETED; - build.Failure = null; - build.UpdatedAtUtc = DateTimeOffset.UtcNow; - await this.store.SaveBuildAsync(build, token); - this.progressService.Publish(build); - diagnostics.FailureCode = VisualBriefingFailureCode.NO_CHANGES; - diagnostics.FinishedAtUtc = DateTimeOffset.UtcNow; - return new( - false, - null, - "The selected briefing version already uses the current compiler and runtime.", - VisualBriefingFailureCode.NO_CHANGES, - diagnostics, - false); - } - - throw new VisualBriefingBuildException( - VisualBriefingFailureCode.STORE_FAILED, - VisualBriefingBuildStage.COMMIT, - revision.Issue, - "The immutable recompiled revision commit was rejected."); - } + throw new VisualBriefingBuildException(VisualBriefingFailureCode.STORE_FAILED, VisualBriefingBuildStage.COMMIT, revision.Issue, "The immutable recompiled revision commit was rejected."); assemblyStage.Status = VisualBriefingBuildStageStatus.COMPLETED; assemblyStage.FinishedAtUtc = DateTimeOffset.UtcNow; @@ -341,4 +314,77 @@ internal sealed partial class VisualBriefingBuildOrchestrator gate.Release(); } } + + /// + /// Reconstructs the most specific model attribution available for each reused semantic artifact. + /// + private async Task> ResolveRecompileModelContributionsAsync(Guid briefingId, VisualBriefingVersion parentVersion, + VisualBriefingEvidenceArtifact evidence, VisualBriefingPlanArtifact plan, VisualBriefingContentArtifact content, VisualBriefingPresentationArtifact presentation, + CancellationToken token) + { + var builds = await this.store.ListBuildsAsync(briefingId, token); + + return + [ + new( + VisualBriefingModelRole.EVIDENCE, + ResolveRecompileModelLabel( + builds, + build => build.EvidenceArtifactId, + evidence.ArtifactId, + VisualBriefingBuildStage.EVIDENCE, + ExistingModelLabel(parentVersion, VisualBriefingModelRole.EVIDENCE, evidence.Model))), + + new( + VisualBriefingModelRole.PLAN, + ResolveRecompileModelLabel( + builds, + build => build.PlanArtifactId, + plan.ArtifactId, + VisualBriefingBuildStage.PLAN, + ExistingModelLabel(parentVersion, VisualBriefingModelRole.PLAN, plan.Model))), + + new( + VisualBriefingModelRole.CONTENT, + ResolveRecompileModelLabel( + builds, + build => build.ContentArtifactId, + content.ArtifactId, + VisualBriefingBuildStage.CONTENT, + ExistingModelLabel(parentVersion, VisualBriefingModelRole.CONTENT, content.Model))), + + new( + VisualBriefingModelRole.DESIGN, + ResolveRecompileModelLabel( + builds, + build => build.PresentationArtifactId, + presentation.ArtifactId, + VisualBriefingBuildStage.DESIGN, + ExistingModelLabel(parentVersion, VisualBriefingModelRole.DESIGN, presentation.Model))), + ]; + } + + /// + /// Resolves the provider and model that originally produced one immutable artifact. + /// + private static string ResolveRecompileModelLabel(IReadOnlyList builds, Func artifactId, + Guid expectedArtifactId, VisualBriefingBuildStage stage, string fallback) + { + var producingBuild = builds.FirstOrDefault(build => + artifactId(build) == expectedArtifactId && + !string.IsNullOrWhiteSpace(build.ProviderFamily) && + !string.IsNullOrWhiteSpace(build.Model) && + build.Stages.Any(candidate => candidate.Stage == stage && candidate.Status is VisualBriefingBuildStageStatus.COMPLETED)); + + return producingBuild is null ? fallback : VisualBriefingModelNames.ExportLabel(producingBuild.ProviderFamily, producingBuild.Model); + } + + /// + /// Returns the persisted role attribution, falling back to the immutable artifact label. + /// + private static string ExistingModelLabel(VisualBriefingVersion parentVersion, VisualBriefingModelRole role, string artifactModel) + { + var contribution = parentVersion.ModelContributions.FirstOrDefault(candidate => candidate.Role == role && !string.IsNullOrWhiteSpace(candidate.Model)); + return contribution?.Model ?? artifactModel; + } } \ No newline at end of file diff --git a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingStore.Versions.cs b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingStore.Versions.cs index 6a36effc..441cdd01 100644 --- a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingStore.Versions.cs +++ b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingStore.Versions.cs @@ -86,7 +86,8 @@ public sealed partial class VisualBriefingStore !string.Equals(parent.AssetHash, hashes.AssetHash, StringComparison.Ordinal))) return VisualBriefingRevisionResult.Failure("A recompile attempted to modify semantic artifacts or embedded assets."); - if (string.Equals(parent.DataHash, hashes.DataHash, StringComparison.Ordinal) && + if (request.EditMode is not VisualBriefingEditMode.RECOMPILE && + string.Equals(parent.DataHash, hashes.DataHash, StringComparison.Ordinal) && string.Equals(parent.AssetHash, hashes.AssetHash, StringComparison.Ordinal) && string.Equals(parent.TemplateHash, hashes.TemplateHash, StringComparison.Ordinal) && string.Equals(parent.CssHash, hashes.CssHash, StringComparison.Ordinal) &&