diff --git a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingArtifactService.Assembly.cs b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingArtifactService.Assembly.cs index 37ef1833f..e635e897e 100644 --- a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingArtifactService.Assembly.cs +++ b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingArtifactService.Assembly.cs @@ -370,7 +370,10 @@ public sealed partial class VisualBriefingArtifactService /// /// Defines AddProtectedArtifactData for the visual briefing feature. /// - private static JsonElement AddProtectedArtifactData(VisualBriefingManifest manifest, VisualBriefingRevisionRequest request) + /// + /// Internal, so that a test can check the JSON it writes. + /// + internal static JsonElement AddProtectedArtifactData(VisualBriefingManifest manifest, VisualBriefingRevisionRequest request) { var source = request.Data; var dictionary = JsonSerializer.Deserialize>(source.GetRawText(), JSON_OPTIONS) ?? []; diff --git a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingContentStage.cs b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingContentStage.cs index f67919758..916f5efce 100644 --- a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingContentStage.cs +++ b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingContentStage.cs @@ -107,7 +107,8 @@ internal sealed class VisualBriefingContentStage(StructuredLlmStageRunner stageR Do not return source references, reset controls, filter controls, or entries for ASSET components; AI Studio creates all of them deterministically. """; - private static string BuildPrompt(VisualBriefingManifest manifest, VisualBriefingEvidenceArtifact evidence, VisualBriefingPlanArtifact plan) + // Internal, so that a test can check the JSON this prompt carries: + internal static string BuildPrompt(VisualBriefingManifest manifest, VisualBriefingEvidenceArtifact evidence, VisualBriefingPlanArtifact plan) { var components = plan.Sections.SelectMany(section => section.Components).ToArray(); var componentIds = components.Select(component => component.ComponentId).ToArray(); diff --git a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingEvidenceStage.cs b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingEvidenceStage.cs index 8ed11cf24..9eb858d4a 100644 --- a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingEvidenceStage.cs +++ b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingEvidenceStage.cs @@ -96,7 +96,8 @@ internal sealed class VisualBriefingEvidenceStage(StructuredLlmStageRunner stage Include only facts supported by the supplied material. """; - private static string BuildPrompt(VisualBriefingManifest manifest, VisualBriefingPreparedSources preparedSources) + // Internal, so that a test can check the JSON this prompt carries: + internal static string BuildPrompt(VisualBriefingManifest manifest, VisualBriefingPreparedSources preparedSources) { // The model never sees internal source GUIDs, only short handles. The file name is what lets // it tell the attached documents apart, which are supplied in the same canonical order: diff --git a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingPlanStage.cs b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingPlanStage.cs index f6cb47c75..22adebf27 100644 --- a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingPlanStage.cs +++ b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingPlanStage.cs @@ -107,7 +107,8 @@ internal sealed class VisualBriefingPlanStage(StructuredLlmStageRunner stageRunn Choose HORIZONTAL for a concise overview with few milestones and VERTICAL for longer or explanation-rich chronological narratives. """; - private static string BuildPrompt(VisualBriefingManifest manifest, VisualBriefingEvidenceArtifact evidence) => + // Internal, so that a test can check the JSON this prompt carries: + internal static string BuildPrompt(VisualBriefingManifest manifest, VisualBriefingEvidenceArtifact evidence) => $""" Audience: {manifest.Settings.AudienceProfile}; {manifest.Settings.AudienceAgeGroup}; {manifest.Settings.AudienceOrganizationalLevel}; {manifest.Settings.AudienceExpertise} Scope instruction: {manifest.Settings.Instruction} diff --git a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingPresentationStage.cs b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingPresentationStage.cs index 720409593..7ae5eac88 100644 --- a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingPresentationStage.cs +++ b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingPresentationStage.cs @@ -181,7 +181,8 @@ internal sealed class VisualBriefingPresentationStage(StructuredLlmStageRunner s MindWork AI Studio owns all colors, typography, surfaces, and chart styling. """; - private static string BuildPrompt(VisualBriefingManifest manifest, VisualBriefingPlanArtifact plan, VisualBriefingPresentationArtifact? parent) + // Internal, so that a test can check the JSON this prompt carries: + internal static string BuildPrompt(VisualBriefingManifest manifest, VisualBriefingPlanArtifact plan, VisualBriefingPresentationArtifact? parent) { var parentJson = parent is null ? "none" : JsonSerializer.Serialize(new { parent.Layout, parent.Profile }, VisualBriefingJson.Canonical); return $""" diff --git a/app/Tests/Serialization/Fixtures/visual-briefing-content-prompt.txt b/app/Tests/Serialization/Fixtures/visual-briefing-content-prompt.txt new file mode 100644 index 000000000..c2e2bd29f --- /dev/null +++ b/app/Tests/Serialization/Fixtures/visual-briefing-content-prompt.txt @@ -0,0 +1,10 @@ +Target language: Use the language English (US) (EN_US) for your output. +Audience: STUDENTS; CHILDREN; TRAINEES; NON_EXPERTS +Scope instruction: instruction sample 11 +Exact planned component IDs: ["chart-line","chart-area","chart-bar","chart-stacked_bar","chart-scatter","chart-pie","chart-donut","chart-radar","simulation"] +Exact keys of accessibilityTexts, no others: ["chart-line","chart-area","chart-bar","chart-stacked_bar","chart-scatter","chart-pie","chart-donut","chart-radar","simulation"] +Exact required slot IDs with their semantic role and declared type, each to be returned exactly once: [{"slotId":"overview-title","role":"TITLE","type":"TEXT"},{"slotId":"overview-summary","role":"SUMMARY","type":"TEXT"},{"slotId":"chart-line-title","role":"TITLE","type":"TEXT"},{"slotId":"chart-line-caption","role":"CAPTION","type":"TEXT"},{"slotId":"chart-area-title","role":"TITLE","type":"TEXT"},{"slotId":"chart-area-caption","role":"CAPTION","type":"TEXT"},{"slotId":"chart-bar-title","role":"TITLE","type":"TEXT"},{"slotId":"chart-bar-caption","role":"CAPTION","type":"TEXT"},{"slotId":"chart-stacked_bar-title","role":"TITLE","type":"TEXT"},{"slotId":"chart-stacked_bar-caption","role":"CAPTION","type":"TEXT"},{"slotId":"chart-scatter-title","role":"TITLE","type":"TEXT"},{"slotId":"chart-scatter-caption","role":"CAPTION","type":"TEXT"},{"slotId":"chart-pie-title","role":"TITLE","type":"TEXT"},{"slotId":"chart-pie-caption","role":"CAPTION","type":"TEXT"},{"slotId":"chart-donut-title","role":"TITLE","type":"TEXT"},{"slotId":"chart-donut-caption","role":"CAPTION","type":"TEXT"},{"slotId":"chart-radar-title","role":"TITLE","type":"TEXT"},{"slotId":"chart-radar-caption","role":"CAPTION","type":"TEXT"},{"slotId":"simulation-title","role":"TITLE","type":"TEXT"},{"slotId":"simulation-summary","role":"SUMMARY","type":"TEXT"},{"slotId":"simulation-result","role":"RESULT","type":"TEXT"}] +Exact chart component IDs, each to receive exactly one chart: ["chart-line","chart-area","chart-bar","chart-stacked_bar","chart-scatter","chart-pie","chart-donut","chart-radar"] +Exact control and formula requirements, no controls for any other component: [{"componentId":"simulation","kind":"SIMULATION","panelSlotIds":[],"resultSlotIds":["simulation-result"]}] +Plan: [{"sectionId":"overview","role":"HERO","titleSlotId":"overview-title","summarySlotId":"overview-summary","components":[{"componentId":"chart-line","kind":"CHART","evidenceIds":["F1"],"slots":[{"slotId":"chart-line-title","role":"TITLE"},{"slotId":"chart-line-caption","role":"CAPTION"}],"assetId":null,"timelineOrientation":null},{"componentId":"chart-area","kind":"CHART","evidenceIds":["F1"],"slots":[{"slotId":"chart-area-title","role":"TITLE"},{"slotId":"chart-area-caption","role":"CAPTION"}],"assetId":null,"timelineOrientation":null},{"componentId":"chart-bar","kind":"CHART","evidenceIds":["F1"],"slots":[{"slotId":"chart-bar-title","role":"TITLE"},{"slotId":"chart-bar-caption","role":"CAPTION"}],"assetId":null,"timelineOrientation":null},{"componentId":"chart-stacked_bar","kind":"CHART","evidenceIds":["F1"],"slots":[{"slotId":"chart-stacked_bar-title","role":"TITLE"},{"slotId":"chart-stacked_bar-caption","role":"CAPTION"}],"assetId":null,"timelineOrientation":null},{"componentId":"chart-scatter","kind":"CHART","evidenceIds":["F1"],"slots":[{"slotId":"chart-scatter-title","role":"TITLE"},{"slotId":"chart-scatter-caption","role":"CAPTION"}],"assetId":null,"timelineOrientation":null},{"componentId":"chart-pie","kind":"CHART","evidenceIds":["F1"],"slots":[{"slotId":"chart-pie-title","role":"TITLE"},{"slotId":"chart-pie-caption","role":"CAPTION"}],"assetId":null,"timelineOrientation":null},{"componentId":"chart-donut","kind":"CHART","evidenceIds":["F1"],"slots":[{"slotId":"chart-donut-title","role":"TITLE"},{"slotId":"chart-donut-caption","role":"CAPTION"}],"assetId":null,"timelineOrientation":null},{"componentId":"chart-radar","kind":"CHART","evidenceIds":["F1"],"slots":[{"slotId":"chart-radar-title","role":"TITLE"},{"slotId":"chart-radar-caption","role":"CAPTION"}],"assetId":null,"timelineOrientation":null},{"componentId":"simulation","kind":"SIMULATION","evidenceIds":["M1"],"slots":[{"slotId":"simulation-title","role":"TITLE"},{"slotId":"simulation-summary","role":"SUMMARY"},{"slotId":"simulation-result","role":"RESULT"}],"assetId":null,"timelineOrientation":null}]}] +Evidence: {"facts":[{"evidenceId":"evidenceId sample 6","statement":"statement sample 7","sourceIds":["sourceIds sample 8","sourceIds sample 9"]},{"evidenceId":"evidenceId sample 10","statement":"statement sample 11","sourceIds":["sourceIds sample 12","sourceIds sample 13"]}],"metrics":[{"evidenceId":"evidenceId sample 14","label":"label sample 15","value":3.75,"unit":"unit sample 16","sourceIds":["sourceIds sample 17","sourceIds sample 18"]},{"evidenceId":"evidenceId sample 19","label":"label sample 20","value":3.75,"unit":"unit sample 21","sourceIds":["sourceIds sample 22","sourceIds sample 23"]}],"tables":[{"evidenceId":"evidenceId sample 24","title":"title sample 25","columns":["columns sample 26","columns sample 27"],"rows":[[{"sample":28,"text":"Gr\u00F6\u00DFe"},{"sample":29,"text":"Gr\u00F6\u00DFe"}],[{"sample":30,"text":"Gr\u00F6\u00DFe"},{"sample":31,"text":"Gr\u00F6\u00DFe"}]],"sourceIds":["sourceIds sample 32","sourceIds sample 33"]},{"evidenceId":"evidenceId sample 34","title":"title sample 35","columns":["columns sample 36","columns sample 37"],"rows":[[{"sample":38,"text":"Gr\u00F6\u00DFe"},{"sample":39,"text":"Gr\u00F6\u00DFe"}],[{"sample":40,"text":"Gr\u00F6\u00DFe"},{"sample":41,"text":"Gr\u00F6\u00DFe"}]],"sourceIds":["sourceIds sample 42","sourceIds sample 43"]}],"assetPlan":[{"assetId":"assetId sample 48","description":"description sample 49","altText":"altText sample 50"},{"assetId":"assetId sample 51","description":"description sample 52","altText":"altText sample 53"}]} \ No newline at end of file diff --git a/app/Tests/Serialization/Fixtures/visual-briefing-evidence-prompt.txt b/app/Tests/Serialization/Fixtures/visual-briefing-evidence-prompt.txt new file mode 100644 index 000000000..225c8d10e --- /dev/null +++ b/app/Tests/Serialization/Fixtures/visual-briefing-evidence-prompt.txt @@ -0,0 +1,4 @@ +Target language: Use the language English (US) (EN_US) for your output. +Scope instruction: instruction sample 11 +Sources, in the same order as the attached files: [{"sourceId":"s1","kind":"VISUAL_ASSET","assetId":"assetId sample 20","name":"path-17.txt"},{"sourceId":"s2","kind":"VISUAL_ASSET","assetId":"assetId sample 25","name":"path-22.txt"}] +Media transcripts: {"s1":"The transcript of a recording, with Gr\u00F6\u00DFe and \u20AC."} \ No newline at end of file diff --git a/app/Tests/Serialization/Fixtures/visual-briefing-plan-prompt.txt b/app/Tests/Serialization/Fixtures/visual-briefing-plan-prompt.txt new file mode 100644 index 000000000..119e3932f --- /dev/null +++ b/app/Tests/Serialization/Fixtures/visual-briefing-plan-prompt.txt @@ -0,0 +1,3 @@ +Audience: STUDENTS; CHILDREN; TRAINEES; NON_EXPERTS +Scope instruction: instruction sample 11 +Evidence: {"facts":[{"evidenceId":"evidenceId sample 6","statement":"statement sample 7","sourceIds":["sourceIds sample 8","sourceIds sample 9"]},{"evidenceId":"evidenceId sample 10","statement":"statement sample 11","sourceIds":["sourceIds sample 12","sourceIds sample 13"]}],"metrics":[{"evidenceId":"evidenceId sample 14","label":"label sample 15","value":3.75,"unit":"unit sample 16","sourceIds":["sourceIds sample 17","sourceIds sample 18"]},{"evidenceId":"evidenceId sample 19","label":"label sample 20","value":3.75,"unit":"unit sample 21","sourceIds":["sourceIds sample 22","sourceIds sample 23"]}],"tables":[{"evidenceId":"evidenceId sample 24","title":"title sample 25","columns":["columns sample 26","columns sample 27"],"rows":[[{"sample":28,"text":"Gr\u00F6\u00DFe"},{"sample":29,"text":"Gr\u00F6\u00DFe"}],[{"sample":30,"text":"Gr\u00F6\u00DFe"},{"sample":31,"text":"Gr\u00F6\u00DFe"}]],"sourceIds":["sourceIds sample 32","sourceIds sample 33"]},{"evidenceId":"evidenceId sample 34","title":"title sample 35","columns":["columns sample 36","columns sample 37"],"rows":[[{"sample":38,"text":"Gr\u00F6\u00DFe"},{"sample":39,"text":"Gr\u00F6\u00DFe"}],[{"sample":40,"text":"Gr\u00F6\u00DFe"},{"sample":41,"text":"Gr\u00F6\u00DFe"}]],"sourceIds":["sourceIds sample 42","sourceIds sample 43"]}],"assetPlan":[{"assetId":"assetId sample 48","description":"description sample 49","altText":"altText sample 50"},{"assetId":"assetId sample 51","description":"description sample 52","altText":"altText sample 53"}]} \ No newline at end of file diff --git a/app/Tests/Serialization/Fixtures/visual-briefing-presentation-prompt.txt b/app/Tests/Serialization/Fixtures/visual-briefing-presentation-prompt.txt new file mode 100644 index 000000000..e52c306c1 --- /dev/null +++ b/app/Tests/Serialization/Fixtures/visual-briefing-presentation-prompt.txt @@ -0,0 +1,6 @@ +Operation: CHANGE_DESIGN +Design instruction: instruction sample 11 +Planned sections and components: +[{"sectionId":"overview","role":"HERO","titleSlotId":"overview-title","summarySlotId":"overview-summary","components":[{"componentId":"chart-line","kind":"CHART","evidenceIds":["F1"],"slots":[{"slotId":"chart-line-title","role":"TITLE"},{"slotId":"chart-line-caption","role":"CAPTION"}],"assetId":null,"timelineOrientation":null},{"componentId":"chart-area","kind":"CHART","evidenceIds":["F1"],"slots":[{"slotId":"chart-area-title","role":"TITLE"},{"slotId":"chart-area-caption","role":"CAPTION"}],"assetId":null,"timelineOrientation":null},{"componentId":"chart-bar","kind":"CHART","evidenceIds":["F1"],"slots":[{"slotId":"chart-bar-title","role":"TITLE"},{"slotId":"chart-bar-caption","role":"CAPTION"}],"assetId":null,"timelineOrientation":null},{"componentId":"chart-stacked_bar","kind":"CHART","evidenceIds":["F1"],"slots":[{"slotId":"chart-stacked_bar-title","role":"TITLE"},{"slotId":"chart-stacked_bar-caption","role":"CAPTION"}],"assetId":null,"timelineOrientation":null},{"componentId":"chart-scatter","kind":"CHART","evidenceIds":["F1"],"slots":[{"slotId":"chart-scatter-title","role":"TITLE"},{"slotId":"chart-scatter-caption","role":"CAPTION"}],"assetId":null,"timelineOrientation":null},{"componentId":"chart-pie","kind":"CHART","evidenceIds":["F1"],"slots":[{"slotId":"chart-pie-title","role":"TITLE"},{"slotId":"chart-pie-caption","role":"CAPTION"}],"assetId":null,"timelineOrientation":null},{"componentId":"chart-donut","kind":"CHART","evidenceIds":["F1"],"slots":[{"slotId":"chart-donut-title","role":"TITLE"},{"slotId":"chart-donut-caption","role":"CAPTION"}],"assetId":null,"timelineOrientation":null},{"componentId":"chart-radar","kind":"CHART","evidenceIds":["F1"],"slots":[{"slotId":"chart-radar-title","role":"TITLE"},{"slotId":"chart-radar-caption","role":"CAPTION"}],"assetId":null,"timelineOrientation":null},{"componentId":"simulation","kind":"SIMULATION","evidenceIds":["M1"],"slots":[{"slotId":"simulation-title","role":"TITLE"},{"slotId":"simulation-summary","role":"SUMMARY"},{"slotId":"simulation-result","role":"RESULT"}],"assetId":null,"timelineOrientation":null}]}] +Parent design: +{"layout":{"nodeId":"nodeId sample 6","kind":"STACK","sectionId":"sectionId sample 7","componentId":"componentId sample 8","children":[],"columns":{"mobile":49,"tablet":50,"desktop":51},"span":52,"order":53,"emphasized":true,"alignment":"CENTER"},"profile":"EXECUTIVE"} \ No newline at end of file diff --git a/app/Tests/Serialization/Fixtures/visual-briefing-protected-data.json b/app/Tests/Serialization/Fixtures/visual-briefing-protected-data.json new file mode 100644 index 000000000..57a7dcc13 --- /dev/null +++ b/app/Tests/Serialization/Fixtures/visual-briefing-protected-data.json @@ -0,0 +1 @@ +{"slots":{"title":"Gr\u00F6\u00DFe"},"_mwai":{"schemaVersion":2,"runtimeVersion":1,"aiStudioVersion":"__VERSION__","assets":{"asset-1":"data:image/png;base64,AA=="},"assetMetadata":{"asset-1":{"description":"A chart of the quarter","altText":"Revenue per quarter, with Gr\u00F6\u00DFe"}},"footer":{"createdWith":"Created with MindWork AI Studio v__VERSION__.","models":"Contributing models: Model A (content); Model B (presentation).","createdAt":"Revision created on 2026-11-03.","authors":"Author(s): author sample 4.","protection":"Protection level: internal."}}} \ No newline at end of file diff --git a/app/Tests/Serialization/VisualBriefingPromptTests.cs b/app/Tests/Serialization/VisualBriefingPromptTests.cs new file mode 100644 index 000000000..1c00e4923 --- /dev/null +++ b/app/Tests/Serialization/VisualBriefingPromptTests.cs @@ -0,0 +1,60 @@ +using System.Reflection; +using System.Text.Json; + +using AIStudio.Assistants.VisualBriefing; +using AIStudio.Tools.Metadata; + +namespace AIStudio.Tests.Serialization; + +/// +/// Checks that the stages of the Visual Briefing assistant send their models the same JSON as before, and that an +/// exported briefing gets the same protected data. +/// +/// +/// The files in the folder Fixtures were written with the contracts of reflection, before AI Studio got +/// contracts of its own. A model reads the JSON of a prompt as it is, so a field under another name or one which +/// went missing changes what it builds. The protected data of an exported briefing is checked when the briefing +/// gets imported again. The fixture of the protected data names the version of AI Studio as a placeholder, +/// because every release changes it. +/// +[TestFixture] +public sealed class VisualBriefingPromptTests +{ + private const string VERSION_PLACEHOLDER = "__VERSION__"; + + private static IEnumerable Prompts() + { + yield return Case("visual-briefing-evidence-prompt.txt", () => + { + var manifest = Manifest(); + return VisualBriefingEvidenceStage.BuildPrompt(manifest, VisualBriefingSamples.PreparedSources(manifest)); + }); + + yield return Case("visual-briefing-plan-prompt.txt", () => VisualBriefingPlanStage.BuildPrompt(Manifest(), Evidence())); + yield return Case("visual-briefing-content-prompt.txt", () => VisualBriefingContentStage.BuildPrompt(Manifest(), Evidence(), VisualBriefingSamples.Plan())); + yield return Case("visual-briefing-presentation-prompt.txt", () => VisualBriefingPresentationStage.BuildPrompt(Manifest(), VisualBriefingSamples.Plan(), Presentation())); + } + + [TestCaseSource(nameof(Prompts))] + public void APromptCarriesTheSameJsonAsBefore(string fileName, Func buildPrompt) => Assert.That(buildPrompt(), Is.EqualTo(JsonGoldenFileTests.ReadFixture(fileName))); + + [Test] + public void AnExportedBriefingGetsTheSameProtectedData() + { + var manifest = Manifest(); + var data = VisualBriefingArtifactService.AddProtectedArtifactData(manifest, VisualBriefingSamples.RevisionRequest(manifest)); + var version = typeof(VisualBriefingArtifactService).Assembly.GetCustomAttribute()?.Version ?? "unknown"; + + Assert.That(data.GetRawText(), Is.EqualTo(JsonGoldenFileTests.ReadFixture("visual-briefing-protected-data.json").Replace(VERSION_PLACEHOLDER, version, StringComparison.Ordinal))); + } + + private static TestCaseData Case(string fileName, Func buildPrompt) => new TestCaseData(fileName, buildPrompt).SetArgDisplayNames(fileName); + + private static VisualBriefingManifest Manifest() => Read("visual-briefing-manifest.json"); + + private static VisualBriefingEvidenceArtifact Evidence() => Read("visual-briefing-evidence-artifact.json"); + + private static VisualBriefingPresentationArtifact Presentation() => Read("visual-briefing-presentation-artifact.json"); + + private static T Read(string fileName) => JsonSerializer.Deserialize(JsonGoldenFileTests.ReadFixture(fileName), VisualBriefingJson.Persistence)!; +} \ No newline at end of file diff --git a/app/Tests/Serialization/VisualBriefingSamples.cs b/app/Tests/Serialization/VisualBriefingSamples.cs index 4e5ba5c21..89ec4cd9a 100644 --- a/app/Tests/Serialization/VisualBriefingSamples.cs +++ b/app/Tests/Serialization/VisualBriefingSamples.cs @@ -145,6 +145,45 @@ internal static class VisualBriefingSamples Kind = VisualBriefingLayoutNodeKind.STACK, }; + /// + /// The prepared sources of a briefing, with a transcript for its first source. + /// + internal static VisualBriefingPreparedSources PreparedSources(VisualBriefingManifest manifest) => new() + { + Transcripts = new Dictionary + { + [manifest.Sources[0].SourceId] = "The transcript of a recording, with Größe and €.", + }, + }; + + /// + /// A new revision of a briefing, with an embedded picture and two models which contributed to it. + /// + internal static VisualBriefingRevisionRequest RevisionRequest(VisualBriefingManifest manifest) => new( + BriefingId: manifest.BriefingId, + ParentRevisionId: null, + EditMode: VisualBriefingEditMode.INITIAL, + Instruction: "Summarize the quarter.", + Data: Json("""{"slots":{"title":"Größe"},"assets":{"old":"x"},"footerTemplates":{},"protectionLabel":"x","_mwai":{}}"""), + TemplateHtml: "
", + Css: "main {}", + ModelDisplayName: "Model A", + Origin: "test", + ModelContributions: + [ + new(VisualBriefingModelRole.CONTENT, "Model A"), + new(VisualBriefingModelRole.DESIGN, "Model B"), + ], + CreatedAtUtc: new DateTimeOffset(2026, 11, 3, 9, 30, 0, TimeSpan.Zero), + EmbeddedAssets: new Dictionary(StringComparer.Ordinal) + { + ["asset-1"] = "data:image/png;base64,AA==", + }, + AssetPlan: + [ + new() { AssetId = "asset-1", Description = "A chart of the quarter", AltText = "Revenue per quarter, with Größe" }, + ]); + private static string ChartId(VisualBriefingChartKind kind) => $"chart-{kind.ToString().ToLowerInvariant()}"; private static JsonElement Json(string json)