Fixed header template and added the app icon

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

View File

@ -12,6 +12,11 @@ namespace AIStudio.Assistants.VisualBriefing;
public sealed partial class VisualBriefingArtifactService
{
/// <summary>
/// Lazily loads the official MindWork AI Studio icon for self-contained exports.
/// </summary>
private static readonly Lazy<string> BRAND_ICON_DATA_URI = new(LoadBrandIconDataUri);
/// <summary>
/// Assembles one self-contained briefing HTML file from validated parts.
/// </summary>
@ -79,10 +84,13 @@ public sealed partial class VisualBriefingArtifactService
<meta name="referrer" content="no-referrer">
<title>{HtmlEncode(briefingName)}</title>
<style id="mwai-briefing-style">{css}</style>
<style id="mwai-protected-style">{PROTECTED_FOOTER_CSS}</style>
<style id="mwai-protected-style">{PROTECTED_STATIC_CSS}</style>
</head>
<body>
<script id="{DATA_ELEMENT_ID}" type="application/json">{dataJson}</script>
<header id="mwai-static-header">
{BuildStaticHeaderTemplate()}
</header>
<div id="mwai-briefing-root">{template}</div>
<footer id="mwai-static-footer" class="mwai-footer">
{STATIC_FOOTER_TEMPLATE}
@ -110,11 +118,39 @@ public sealed partial class VisualBriefingArtifactService
[GeneratedRegex("""const AI_STUDIO_VERSION = (?<value>"(?:\\.|[^"\\])*");""", RegexOptions.CultureInvariant)]
private static partial Regex RuntimeAIVersionRegex();
/// <summary>
/// Builds the protected, app-owned static header template.
/// </summary>
private static string BuildStaticHeaderTemplate() => $"""
<img src="{BRAND_ICON_DATA_URI.Value}" width="32" height="32" alt="" aria-hidden="true">
<a href="{PROJECT_URL}" target="_blank" rel="noopener noreferrer">MINDWORK AI STUDIO</a>
""";
/// <summary>
/// Loads the official app icon as a Data URL so exported briefings remain self-contained.
/// </summary>
private static string LoadBrandIconDataUri()
{
var assembly = Assembly.GetExecutingAssembly();
using var stream = assembly.GetManifestResourceStream("AIStudio.Assistants.VisualBriefing.Runtime.mindwork-ai-studio-icon.png") ??
throw new InvalidOperationException("The official MindWork AI Studio icon is not available in this build.");
using var buffer = new MemoryStream();
stream.CopyTo(buffer);
return $"data:image/png;base64,{Convert.ToBase64String(buffer.ToArray())}";
}
/// <summary>
/// Links exported MindWork AI Studio branding to the project repository.
/// </summary>
private const string PROJECT_URL = "https://github.com/MindWorkAI/AI-Studio";
/// <summary>
/// Defines the protected, app-owned static footer template.
/// </summary>
private const string STATIC_FOOTER_TEMPLATE = """
<span>Created with <a href="https://github.com/MindWorkAI/AI-Studio" target="_blank" rel="noopener noreferrer">MindWork AI Studio</a> v<span data-mwai-text="_mwai.aiStudioVersion"></span>.</span>
private const string STATIC_FOOTER_TEMPLATE = $"""
<span>Created with <a href="{PROJECT_URL}" target="_blank" rel="noopener noreferrer">MindWork AI Studio</a> v<span data-mwai-text="_mwai.aiStudioVersion"></span>.</span>
<span data-mwai-text="_mwai.footer.models"></span>
<span data-mwai-text="_mwai.footer.createdAt"></span>
<span data-mwai-text="_mwai.footer.authors"></span>
@ -122,9 +158,9 @@ public sealed partial class VisualBriefingArtifactService
""";
/// <summary>
/// Defines protected footer styles that model CSS cannot override.
/// Defines protected static header and footer styles that model CSS cannot override.
/// </summary>
private const string PROTECTED_FOOTER_CSS = """
private const string PROTECTED_STATIC_CSS = """
html {
background: #f3f6f3 !important;
}
@ -134,6 +170,50 @@ public sealed partial class VisualBriefingArtifactService
background: #f3f6f3 !important;
color: #172a24 !important;
}
#mwai-static-header {
display: flex !important;
align-items: center !important;
gap: .75rem !important;
position: relative !important;
z-index: 2147483647 !important;
visibility: visible !important;
opacity: 1 !important;
max-width: 80rem !important;
margin: 0 auto !important;
padding: clamp(1rem, 3.5vw, 3rem) clamp(1rem, 3.5vw, 3rem) 0 !important;
color: #164b3b !important;
font: 700 .82rem/1.4 system-ui, sans-serif !important;
letter-spacing: .08em !important;
text-transform: uppercase !important;
}
#mwai-static-header img {
box-sizing: border-box !important;
display: block !important;
flex: 0 0 auto !important;
visibility: visible !important;
opacity: 1 !important;
width: 2rem !important;
height: 2rem !important;
border-radius: .5rem !important;
object-fit: cover !important;
}
#mwai-static-header a {
display: inline !important;
visibility: visible !important;
opacity: 1 !important;
color: inherit !important;
font: inherit !important;
letter-spacing: inherit !important;
text-decoration: none !important;
}
#mwai-static-header a:hover {
text-decoration: underline !important;
text-underline-offset: .2em !important;
}
#mwai-static-header a:focus-visible {
outline: 3px solid #f2d264 !important;
outline-offset: 3px !important;
}
#mwai-static-footer {
display: flex !important;
flex-wrap: wrap !important;
@ -163,10 +243,19 @@ public sealed partial class VisualBriefingArtifactService
text-decoration: underline !important;
text-underline-offset: .15em !important;
}
@media (max-width: 47.99rem) {
#mwai-static-header {
padding: .75rem .75rem 0 !important;
}
}
@media print {
html, body {
background: #fffefa !important;
}
#mwai-static-header {
max-width: none !important;
padding: 0 0 12mm !important;
}
#mwai-static-footer {
max-width: none !important;
margin-top: 6mm !important;

View File

@ -118,6 +118,7 @@ public sealed partial class VisualBriefingArtifactService
var dataNode = FindUniqueElementById(document, DATA_ELEMENT_ID);
var rootNode = FindUniqueElementById(document, "mwai-briefing-root");
var footerNode = FindUniqueElementById(document, "mwai-static-footer");
var headerNodes = FindNodes(document.DocumentNode, "//*[@id='mwai-static-header']")?.ToArray() ?? [];
var generatedStyleNode = FindUniqueElementById(document, "mwai-briefing-style");
var protectedStyleNode = FindUniqueElementById(document, "mwai-protected-style");
var runtimeNode = FindUniqueElementById(document, "mwai-briefing-runtime");
@ -128,6 +129,9 @@ public sealed partial class VisualBriefingArtifactService
if (htmlNode is null || headNode is null || bodyNode is null || dataNode is null || rootNode is null ||
footerNode is null || generatedStyleNode is null || protectedStyleNode is null || runtimeNode is null ||
headerNodes.Length > 1 ||
(headerNodes.Length == 1 &&
(!headerNodes[0].Name.Equals("header", StringComparison.OrdinalIgnoreCase) || headerNodes[0].ParentNode != bodyNode)) ||
!styleMatch.Success || !runtimeMatch.Success || (echartsNode is not null) != echartsMatch.Success)
{
issue = "The briefing envelope is incomplete or ambiguous.";
@ -153,6 +157,7 @@ public sealed partial class VisualBriefingArtifactService
var allowedBodyIds = new HashSet<string>(StringComparer.Ordinal)
{
DATA_ELEMENT_ID,
"mwai-static-header",
"mwai-briefing-root",
"mwai-static-footer",
"mwai-echarts-runtime",

View File

@ -120,10 +120,7 @@ internal sealed class VisualBriefingLayoutCompiler
var layoutClasses = CompileLayoutClasses(node, $"mwai-layout mwai-{kind}");
if (isRoot)
{
var masthead = "<header class=\"mwai-masthead\"><div class=\"mwai-brand-mark\" aria-hidden=\"true\"><span></span><span></span><span></span><i></i></div><strong data-mwai-text=\"labels.brand\"></strong></header>";
return $"<main id=\"{id}\" class=\"{layoutClasses} mwai-document\">{masthead}{children}</main>";
}
return $"<main id=\"{id}\" class=\"{layoutClasses} mwai-document\">{children}</main>";
return $"<div id=\"{id}\" class=\"{layoutClasses}\">{children}</div>";
}
@ -248,13 +245,9 @@ internal sealed class VisualBriefingLayoutCompiler
};
var css = new StringBuilder($$"""
#mwai-briefing-root{--mwai-ink:#172A24;--mwai-forest:#164B3B;--mwai-pine:#236A50;--mwai-sage:#79AE90;--mwai-cream:#F7F1DC;--mwai-paper:#FFFEFA;--mwai-sun:#F2D264;--mwai-mist:#EAF1EC;--mwai-clay:#C97857;--mwai-line:#D6E2DC;--mwai-muted:#5E7169;--mwai-type-scale:{{typeScale}};--mwai-rhythm:{{rhythm}};--mwai-section-space:{{sectionSpace}};max-width:80rem;margin-inline:auto;padding:clamp(1rem,3.5vw,3rem);font:calc(1rem*var(--mwai-type-scale))/1.65 system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;color:var(--mwai-ink);}
#mwai-briefing-root{--mwai-ink:#172A24;--mwai-forest:#164B3B;--mwai-pine:#236A50;--mwai-sage:#79AE90;--mwai-cream:#F7F1DC;--mwai-paper:#FFFEFA;--mwai-sun:#F2D264;--mwai-mist:#EAF1EC;--mwai-clay:#C97857;--mwai-line:#D6E2DC;--mwai-muted:#5E7169;--mwai-type-scale:{{typeScale}};--mwai-rhythm:{{rhythm}};--mwai-section-space:{{sectionSpace}};max-width:80rem;margin-inline:auto;padding:clamp(1rem,2.5vw,2rem) clamp(1rem,3.5vw,3rem) clamp(1rem,3.5vw,3rem);font:calc(1rem*var(--mwai-type-scale))/1.65 system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;color:var(--mwai-ink);}
#mwai-briefing-root *{box-sizing:border-box;}
.mwai-document{display:flex;flex-direction:column;gap:clamp(1rem,2.5vw,2rem);}
.mwai-masthead{display:flex;align-items:center;gap:.75rem;padding:.35rem .25rem;color:var(--mwai-forest);font-size:.82rem;letter-spacing:.08em;text-transform:uppercase;}
.mwai-brand-mark{position:relative;display:flex;align-items:center;gap:3px;width:2rem;height:1.55rem;padding-inline:.42rem;border-radius:.55rem;background:var(--mwai-cream);border:1px solid color-mix(in srgb,var(--mwai-pine),transparent 70%);}
.mwai-brand-mark span{display:block;width:4px;height:4px;border-radius:50%;background:var(--mwai-pine);}
.mwai-brand-mark i{position:absolute;left:.35rem;bottom:-.22rem;width:.45rem;height:.45rem;background:var(--mwai-cream);border-inline-start:1px solid color-mix(in srgb,var(--mwai-pine),transparent 70%);transform:skewY(-35deg);}
.mwai-section{display:block;border-radius:clamp(1.25rem,2.5vw,2rem);}
.mwai-section-inner{padding:clamp(2rem,5vw,var(--mwai-section-space));}
.mwai-section-heading{max-width:52rem;margin-block-end:clamp(1.75rem,4vw,3.25rem);}
@ -356,8 +349,8 @@ internal sealed class VisualBriefingLayoutCompiler
css.Append("""
@media screen and (min-width:48rem){.mwai-timeline-horizontal .mwai-timeline-track{display:grid;grid-auto-flow:column;grid-auto-columns:minmax(13rem,1fr);overflow-x:auto;padding:.55rem 0 .5rem;padding-inline-start:.55rem}.mwai-timeline-horizontal .mwai-timeline-item{padding:0;padding-block-start:1.5rem;padding-inline-end:1rem;border-block-start:2px solid var(--mwai-line);border-inline-start:0}.mwai-timeline-horizontal .mwai-timeline-marker{inset-block-start:-.52rem;inset-inline-start:-.52rem}}
@media(max-width:47.99rem){#mwai-briefing-root{padding:.75rem}.mwai-section-inner{padding:1.5rem}.mwai-section-hero .mwai-section-inner{min-height:34rem}.mwai-metric,.mwai-chart,.mwai-asset,.mwai-table,.mwai-filterable_table,.mwai-tabs,.mwai-accordion,.mwai-simulation,.mwai-timeline{padding:1rem}[data-mwai-chart]{min-height:19rem}th,td{padding:.7rem .75rem}}
@media print{@page{margin:14mm}#mwai-briefing-root{max-width:none;padding:0;font-size:10pt}.mwai-document{gap:8mm}.mwai-masthead{padding:0 0 4mm}.mwai-section{border:0;box-shadow:none;background:transparent;color:var(--mwai-ink);break-inside:auto}.mwai-section-inner{padding:6mm 0}.mwai-section-heading{margin-block-end:5mm}.mwai-section-heading h1{font-size:28pt}.mwai-section-heading h2{font-size:21pt}.mwai-section-heading p,.mwai-section-hero .mwai-section-heading p,.mwai-section-conclusion .mwai-section-heading p{color:var(--mwai-muted)}.mwai-component,.mwai-component figure,.mwai-table-wrap{break-inside:avoid}.mwai-timeline{break-inside:auto}.mwai-timeline-item{break-inside:avoid}.mwai-metric,.mwai-chart,.mwai-asset,.mwai-table,.mwai-filterable_table,.mwai-tabs,.mwai-accordion,.mwai-simulation,.mwai-timeline{box-shadow:none;background:var(--mwai-paper)}[data-mwai-tab-panel][hidden]{display:block!important}details:not([open])>.mwai-accordion-body{display:block!important}[data-mwai-reset]{display:none!important}thead th{position:static}*{print-color-adjust:exact}}
@media(max-width:47.99rem){#mwai-briefing-root{padding:1rem .75rem .75rem}.mwai-section-inner{padding:1.5rem}.mwai-section-hero .mwai-section-inner{min-height:34rem}.mwai-metric,.mwai-chart,.mwai-asset,.mwai-table,.mwai-filterable_table,.mwai-tabs,.mwai-accordion,.mwai-simulation,.mwai-timeline{padding:1rem}[data-mwai-chart]{min-height:19rem}th,td{padding:.7rem .75rem}}
@media print{@page{margin:14mm}#mwai-briefing-root{max-width:none;padding:0;font-size:10pt}.mwai-document{gap:8mm}.mwai-section{border:0;box-shadow:none;background:transparent;color:var(--mwai-ink);break-inside:auto}.mwai-section-inner{padding:6mm 0}.mwai-section-heading{margin-block-end:5mm}.mwai-section-heading h1{font-size:28pt}.mwai-section-heading h2{font-size:21pt}.mwai-section-heading p,.mwai-section-hero .mwai-section-heading p,.mwai-section-conclusion .mwai-section-heading p{color:var(--mwai-muted)}.mwai-component,.mwai-component figure,.mwai-table-wrap{break-inside:avoid}.mwai-timeline{break-inside:auto}.mwai-timeline-item{break-inside:avoid}.mwai-metric,.mwai-chart,.mwai-asset,.mwai-table,.mwai-filterable_table,.mwai-tabs,.mwai-accordion,.mwai-simulation,.mwai-timeline{box-shadow:none;background:var(--mwai-paper)}[data-mwai-tab-panel][hidden]{display:block!important}details:not([open])>.mwai-accordion-body{display:block!important}[data-mwai-reset]{display:none!important}thead th{position:static}*{print-color-adjust:exact}}
""");
return css.ToString();

View File

@ -18,7 +18,7 @@ public static class VisualBriefingVersions
/// Gets the deterministic HTML, CSS, chart, and interaction compiler version. Increment this
/// whenever compiler behavior changes so interrupted recompiles cannot resume across versions.
/// </summary>
public const int COMPILER = 2;
public const int COMPILER = 3;
/// <summary>
/// Gets the embedded AI Studio runtime bundle version. Increment this for changes to the

View File

@ -47,6 +47,7 @@
<EmbeddedResource Include="Assistants\PromptOptimizer\prompting_guideline.md" CopyToOutputDirectory="PreserveNewest" />
<EmbeddedResource Include="Assistants\Builder\AssistantBuilderLuaResponse.schema.json" CopyToOutputDirectory="PreserveNewest" />
<EmbeddedResource Include="Assistants\VisualBriefing\Runtime\echarts.common.min.js" CopyToOutputDirectory="PreserveNewest" />
<EmbeddedResource Include="..\..\runtime\icons\128x128.png" LogicalName="AIStudio.Assistants.VisualBriefing.Runtime.mindwork-ai-studio-icon.png" />
</ItemGroup>
<ItemGroup>