mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-08-11 19:32:10 +00:00
Added an option for timelines
This commit is contained in:
parent
3f2cae123b
commit
4bbeccf54c
@ -37,4 +37,7 @@ public enum VisualBriefingComponentKind
|
||||
|
||||
/// <summary>Provides deterministic interactive controls and calculated results.</summary>
|
||||
SIMULATION,
|
||||
|
||||
/// <summary>Displays an ordered chronological sequence without a chart runtime.</summary>
|
||||
TIMELINE,
|
||||
}
|
||||
@ -101,6 +101,7 @@ internal sealed class VisualBriefingContentStage(StructuredLlmStageRunner stageR
|
||||
Fulfil every required slot from the plan exactly once and add no other slots. Every slot has a declared type in the user message.
|
||||
A TEXT slot value is a JSON string, number, or boolean. Write plain prose without markup, without angle brackets, and without programming syntax.
|
||||
A TABLE slot value is the object {"columns": ["..."], "rows": [{"cells": ["..."]}]}. It has no other properties, every row has exactly one cell per column, and every cell is a string, number, or boolean.
|
||||
A TIMELINE slot value is the object {"items": [{"period": "...", "title": "...", "description": "..."}]}. It has no other properties, contains at least two items in chronological order, and every item has exactly those three non-empty target-language strings.
|
||||
For a FILTERABLE_TABLE component the first column is what readers filter by, so make it a repeating text category and give every row a string in that column.
|
||||
Charts contain componentId, kind (LINE, AREA, BAR, STACKED_BAR, SCATTER, PIE, DONUT, RADAR), categories, and series. Never return chart-library options.
|
||||
Controls contain controlId, componentId, kind (TAB, NUMBER, RANGE, SELECT), initialValue, and typed options with value and label. controlId is a unique lowercase identifier. An option value is the short unique value the control selects, and the option label is its visible target-language text.
|
||||
@ -112,6 +113,7 @@ internal sealed class VisualBriefingContentStage(StructuredLlmStageRunner stageR
|
||||
An accessibilityTexts entry is never shown on screen. It reaches people who cannot see the component, so it states what the component conveys: for a chart the trend and the decisive numbers, for a component with controls what those controls change.
|
||||
Section TITLE and SUMMARY slots and component TITLE, LABEL, EYEBROW, and CAPTION slots are concise display copy. BODY and SUMMARY slots use short paragraphs suitable for screen reading.
|
||||
For ACCORDION components, the TITLE slot supplies the visible summary and the BODY slot supplies the expandable content.
|
||||
For TIMELINE components, preserve the evidence-backed chronology and express dates, ranges, or named phases in period without inventing precision.
|
||||
Do not return source references, reset controls, filter controls, or entries for ASSET components; AI Studio creates all of them deterministically.
|
||||
""";
|
||||
|
||||
|
||||
@ -97,6 +97,7 @@ internal sealed class VisualBriefingEvidenceStage(StructuredLlmStageRunner stage
|
||||
tables contain evidenceId, title, columns, rows, sourceIds; every row has exactly the column count.
|
||||
sourceCoverage contains each supplied source exactly once with coverage USED, CONTEXTUAL, or OUT_OF_SCOPE and a short reason.
|
||||
assetPlan contains each supplied visual asset exactly once with assetId, description, and target-language altText.
|
||||
Preserve material dates, periods, phases, milestones, durations, and their chronological order in the facts or tables that best represent them.
|
||||
Include only facts supported by the supplied material.
|
||||
""";
|
||||
|
||||
|
||||
@ -83,7 +83,18 @@ internal sealed class VisualBriefingLayoutCompiler
|
||||
|
||||
var componentId = HtmlEncoder.Default.Encode(component.ComponentId);
|
||||
var body = CompileComponent(component, content);
|
||||
var componentClasses = CompileLayoutClasses(node, $"mwai-component mwai-{component.Kind.ToString().ToLowerInvariant()}");
|
||||
var semanticClasses = $"mwai-component mwai-{component.Kind.ToString().ToLowerInvariant()}";
|
||||
if (component.Kind is VisualBriefingComponentKind.TIMELINE)
|
||||
{
|
||||
semanticClasses += component.TimelineOrientation switch
|
||||
{
|
||||
VisualBriefingTimelineOrientation.HORIZONTAL => " mwai-timeline-horizontal",
|
||||
VisualBriefingTimelineOrientation.VERTICAL => " mwai-timeline-vertical",
|
||||
_ => throw new InvalidDataException("A timeline component has an invalid orientation."),
|
||||
};
|
||||
}
|
||||
|
||||
var componentClasses = CompileLayoutClasses(node, semanticClasses);
|
||||
|
||||
return $"<article id=\"{id}\" class=\"{componentClasses}\" data-mwai-region=\"{componentId}\">{body}</article>";
|
||||
}
|
||||
@ -136,6 +147,7 @@ internal sealed class VisualBriefingLayoutCompiler
|
||||
VisualBriefingComponentKind.TABS => CompileTabs(component, content.Controls),
|
||||
VisualBriefingComponentKind.ACCORDION => $"<details><summary><span data-mwai-text=\"slots.{Slot(component, VisualBriefingSlotRole.TITLE)}\"></span></summary><div class=\"mwai-accordion-body\"><p data-mwai-text=\"slots.{Slot(component, VisualBriefingSlotRole.BODY)}\"></p></div></details>",
|
||||
VisualBriefingComponentKind.SIMULATION => CompileSimulation(component, controls, content),
|
||||
VisualBriefingComponentKind.TIMELINE => CompileTimeline(component),
|
||||
|
||||
_ => string.Empty,
|
||||
};
|
||||
@ -207,6 +219,19 @@ internal sealed class VisualBriefingLayoutCompiler
|
||||
return $"<fieldset><legend data-mwai-text=\"slots.{title}\"></legend><p data-mwai-text=\"slots.{summary}\"></p><div class=\"mwai-control-grid\">{controls}</div><div class=\"mwai-results\">{outputs}</div>{VisualBriefingInteractionCompiler.CompileResetMarkup(component.ComponentId)}</fieldset>";
|
||||
}
|
||||
|
||||
private static string CompileTimeline(VisualBriefingPlanComponent component)
|
||||
{
|
||||
var title = Slot(component, VisualBriefingSlotRole.TITLE);
|
||||
var summary = Slot(component, VisualBriefingSlotRole.SUMMARY);
|
||||
var dataSlot = Slot(component, VisualBriefingSlotRole.TIMELINE_DATA);
|
||||
|
||||
return $"<header class=\"mwai-component-heading\"><h3 data-mwai-text=\"slots.{title}\"></h3><p data-mwai-text=\"slots.{summary}\"></p></header>" +
|
||||
$"<ol class=\"mwai-timeline-track\" role=\"list\"><template data-mwai-each=\"slots.{dataSlot}.items\"><li class=\"mwai-timeline-item\">" +
|
||||
"<span class=\"mwai-timeline-marker\" aria-hidden=\"true\"></span><div class=\"mwai-timeline-content\">" +
|
||||
"<p class=\"mwai-timeline-period\" data-mwai-text=\".period\"></p><h4 data-mwai-text=\".title\"></h4>" +
|
||||
"<p class=\"mwai-timeline-description\" data-mwai-text=\".description\"></p></div></li></template></ol>";
|
||||
}
|
||||
|
||||
private static string Slot(VisualBriefingPlanComponent component, VisualBriefingSlotRole role, int occurrence = 0)
|
||||
{
|
||||
var slot = component.Slots.Where(candidate => candidate.Role == role).ElementAtOrDefault(occurrence) ?? throw new InvalidDataException($"A {component.Kind} component is missing its {role} slot.");
|
||||
@ -255,7 +280,7 @@ internal sealed class VisualBriefingLayoutCompiler
|
||||
.mwai-component-heading h3,.mwai-callout h3{font-size:clamp(1.3rem,2.2vw,1.75rem);}
|
||||
.mwai-component-heading p,.mwai-copy,.mwai-context,.mwai-callout p{margin:0;max-width:70ch;}
|
||||
.mwai-text{max-width:72ch;padding-block:.5rem;}
|
||||
.mwai-metric,.mwai-chart,.mwai-asset,.mwai-table,.mwai-filterable_table,.mwai-tabs,.mwai-accordion,.mwai-simulation{padding:clamp(1.25rem,2.5vw,2rem);border:1px solid var(--mwai-line);border-radius:1.25rem;background:color-mix(in srgb,var(--mwai-paper),transparent 3%);box-shadow:0 18px 55px rgba(22,75,59,.07);}
|
||||
.mwai-metric,.mwai-chart,.mwai-asset,.mwai-table,.mwai-filterable_table,.mwai-tabs,.mwai-accordion,.mwai-simulation,.mwai-timeline{padding:clamp(1.25rem,2.5vw,2rem);border:1px solid var(--mwai-line);border-radius:1.25rem;background:color-mix(in srgb,var(--mwai-paper),transparent 3%);box-shadow:0 18px 55px rgba(22,75,59,.07);}
|
||||
.mwai-metric{position:relative;overflow:hidden;border-block-start:5px solid var(--mwai-sun);box-shadow:none;}
|
||||
.mwai-metric-body{display:flex;flex-direction:column;margin:0;}
|
||||
.mwai-metric dt{order:2;color:var(--mwai-muted);font-size:.82rem;font-weight:700;letter-spacing:.07em;text-transform:uppercase;}
|
||||
@ -295,6 +320,15 @@ internal sealed class VisualBriefingLayoutCompiler
|
||||
.mwai-control-grid{display:flex;flex-wrap:wrap;gap:1rem;margin-block:1.25rem;}
|
||||
.mwai-results{display:flex;flex-wrap:wrap;gap:.75rem;margin-block:1rem;}
|
||||
.mwai-results output{display:block;min-width:8rem;padding:1rem;border-radius:.8rem;background:var(--mwai-cream);color:var(--mwai-forest);font-size:1.45rem;font-weight:750;}
|
||||
.mwai-timeline-track{display:flex;flex-direction:column;list-style:none;margin:0;padding:0;padding-inline-start:.55rem;}
|
||||
.mwai-timeline-item{position:relative;min-width:0;padding:0;padding-block-end:1.75rem;padding-inline-start:1.75rem;border-inline-start:2px solid var(--mwai-line);}
|
||||
.mwai-timeline-item:last-child{padding-block-end:0;}
|
||||
.mwai-timeline-marker{position:absolute;inset-block-start:.18rem;inset-inline-start:-.52rem;width:.95rem;height:.95rem;border:3px solid var(--mwai-paper);border-radius:50%;background:var(--mwai-pine);box-shadow:0 0 0 2px var(--mwai-sage);}
|
||||
.mwai-timeline-content{display:flex;flex-direction:column;gap:.4rem;}
|
||||
.mwai-timeline-period,.mwai-timeline-description{margin:0;}
|
||||
.mwai-timeline-period{color:var(--mwai-pine);font-size:.78rem;font-weight:760;letter-spacing:.07em;text-transform:uppercase;}
|
||||
.mwai-timeline-content h4{margin:0;color:var(--mwai-forest);font-size:1.08rem;line-height:1.25;}
|
||||
.mwai-timeline-description{color:var(--mwai-muted);line-height:1.55;}
|
||||
.mwai-sources{display:block;padding-block-start:.8rem;border-block-start:1px solid var(--mwai-line);color:var(--mwai-muted);font-size:.76rem;line-height:1.5;}
|
||||
.mwai-emphasized{border-color:var(--mwai-sun);box-shadow:0 18px 55px rgba(22,75,59,.12);}
|
||||
.mwai-align-start{align-items:start;}.mwai-align-center{align-items:center;}.mwai-align-end{align-items:end;}.mwai-align-stretch{align-items:stretch;}
|
||||
@ -321,8 +355,9 @@ internal sealed class VisualBriefingLayoutCompiler
|
||||
}
|
||||
|
||||
css.Append("""
|
||||
@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{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-metric,.mwai-chart,.mwai-asset,.mwai-table,.mwai-filterable_table,.mwai-tabs,.mwai-accordion,.mwai-simulation{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 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}}
|
||||
""");
|
||||
|
||||
return css.ToString();
|
||||
|
||||
@ -27,4 +27,8 @@ public sealed class VisualBriefingPlanComponent
|
||||
/// <summary>Gets or sets the optional embedded asset identifier.</summary>
|
||||
[JsonRequired]
|
||||
public string? AssetId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the orientation used only by timeline components.</summary>
|
||||
[JsonRequired]
|
||||
public VisualBriefingTimelineOrientation? TimelineOrientation { get; set; }
|
||||
}
|
||||
@ -55,7 +55,7 @@ internal sealed class VisualBriefingPlanStage(StructuredLlmStageRunner stageRunn
|
||||
var structuralSignature = VisualBriefingHashing.Compute(string.Join('\u001f', sections.Select(section => $"{section.SectionId}:{section.Role}:{section.TitleSlotId}:{section.SummarySlotId}")
|
||||
.Concat(sections.SelectMany(section => section.Components)
|
||||
.Select(component =>
|
||||
$"{component.ComponentId}:{component.Kind}:{component.AssetId}:{string.Join(',', component.Slots.Select(slot => $"{slot.SlotId}:{slot.Role}"))}"))));
|
||||
$"{component.ComponentId}:{component.Kind}:{component.AssetId}:{component.TimelineOrientation}:{string.Join(',', component.Slots.Select(slot => $"{slot.SlotId}:{slot.Role}"))}"))));
|
||||
|
||||
var artifact = new VisualBriefingPlanArtifact
|
||||
{
|
||||
@ -88,9 +88,9 @@ internal sealed class VisualBriefingPlanStage(StructuredLlmStageRunner stageRunn
|
||||
Section roles are HERO, EXECUTIVE_SUMMARY, NARRATIVE, EVIDENCE, EXPLORATION, or CONCLUSION.
|
||||
The first section is the only HERO. EXECUTIVE_SUMMARY may occur once directly after it. CONCLUSION may occur once as the final section.
|
||||
Every titleSlotId and summarySlotId is a unique content slot ID.
|
||||
Each component has exactly componentId, kind, evidenceIds, slots, and assetId.
|
||||
Every slot has exactly slotId and role. Slot roles are EYEBROW, TITLE, SUMMARY, BODY, LABEL, VALUE, CONTEXT, CAPTION, TABLE_DATA, PANEL, or RESULT.
|
||||
Allowed kinds: TEXT, METRIC, TABLE, CHART, ASSET, CALLOUT, TABS, ACCORDION, FILTERABLE_TABLE, SIMULATION.
|
||||
Each component has exactly componentId, kind, evidenceIds, slots, assetId, and timelineOrientation.
|
||||
Every slot has exactly slotId and role. Slot roles are EYEBROW, TITLE, SUMMARY, BODY, LABEL, VALUE, CONTEXT, CAPTION, TABLE_DATA, PANEL, RESULT, or TIMELINE_DATA.
|
||||
Allowed kinds: TEXT, METRIC, TABLE, CHART, ASSET, CALLOUT, TABS, ACCORDION, FILTERABLE_TABLE, SIMULATION, TIMELINE.
|
||||
IDs are stable lowercase identifiers matching ^[a-z][a-z0-9_-]{0,63}$. Reference only supplied evidence IDs.
|
||||
Slot IDs are unique across the whole briefing, including section title and summary slots.
|
||||
Use these exact component slot patterns:
|
||||
@ -102,7 +102,11 @@ internal sealed class VisualBriefingPlanStage(StructuredLlmStageRunner stageRunn
|
||||
TABS: TITLE, SUMMARY, then one or more PANEL slots.
|
||||
ACCORDION: TITLE, BODY.
|
||||
SIMULATION: TITLE, SUMMARY, then one or more RESULT slots.
|
||||
TIMELINE: TITLE, SUMMARY, TIMELINE_DATA.
|
||||
assetId is null except for ASSET components; include every supplied assetId in exactly one ASSET component.
|
||||
timelineOrientation is null except for TIMELINE components, where it is HORIZONTAL or VERTICAL.
|
||||
Use TIMELINE for sourced events, milestones, phases, or historical developments whose sequence matters; use CHART instead for quantitative trends over time.
|
||||
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) =>
|
||||
|
||||
@ -180,6 +180,7 @@ internal sealed class VisualBriefingPresentationStage(StructuredLlmStageRunner s
|
||||
The layout root is one STACK. Its direct children are one SECTION for every planned section,
|
||||
in plan order, with the matching sectionId. A section may contain STACK and GRID containers,
|
||||
and must reference exactly its own components. Reference every supplied component exactly once.
|
||||
Give a HORIZONTAL TIMELINE enough width for its ordered track; do not place it in a narrow grid column.
|
||||
Prefer editorial rhythm over a wall of cards. Use emphasis sparingly for decisive metrics or insights.
|
||||
MindWork AI Studio owns all colors, typography, surfaces, and chart styling.
|
||||
""";
|
||||
|
||||
@ -40,4 +40,7 @@ public enum VisualBriefingSlotRole
|
||||
|
||||
/// <summary>Provides a calculated simulation result.</summary>
|
||||
RESULT,
|
||||
|
||||
/// <summary>Provides the ordered entries of a chronological timeline.</summary>
|
||||
TIMELINE_DATA,
|
||||
}
|
||||
@ -13,4 +13,7 @@ public enum VisualBriefingSlotType
|
||||
|
||||
/// <summary>A tabular object with columns and rows.</summary>
|
||||
TABLE,
|
||||
|
||||
/// <summary>An ordered object containing chronological timeline items.</summary>
|
||||
TIMELINE,
|
||||
}
|
||||
@ -12,7 +12,12 @@ internal static class VisualBriefingSlotTypes
|
||||
/// </summary>
|
||||
/// <param name="slot">The planned semantic slot.</param>
|
||||
/// <returns>The required slot type.</returns>
|
||||
internal static VisualBriefingSlotType Expected(VisualBriefingPlanSlot slot) => slot.Role is VisualBriefingSlotRole.TABLE_DATA ? VisualBriefingSlotType.TABLE : VisualBriefingSlotType.TEXT;
|
||||
internal static VisualBriefingSlotType Expected(VisualBriefingPlanSlot slot) => slot.Role switch
|
||||
{
|
||||
VisualBriefingSlotRole.TABLE_DATA => VisualBriefingSlotType.TABLE,
|
||||
VisualBriefingSlotRole.TIMELINE_DATA => VisualBriefingSlotType.TIMELINE,
|
||||
_ => VisualBriefingSlotType.TEXT,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a slot carries the tabular data of a table component.
|
||||
@ -51,6 +56,7 @@ internal static class VisualBriefingSlotTypes
|
||||
internal static string Describe(VisualBriefingSlotType type) => type switch
|
||||
{
|
||||
VisualBriefingSlotType.TABLE => "object with a columns array and a rows array of cells arrays",
|
||||
VisualBriefingSlotType.TIMELINE => "object with an items array of period, title, and description strings",
|
||||
_ => "string, number, or boolean",
|
||||
};
|
||||
|
||||
@ -66,6 +72,9 @@ internal static class VisualBriefingSlotTypes
|
||||
return value.ValueKind is JsonValueKind.String or JsonValueKind.Number or JsonValueKind.True or JsonValueKind.False
|
||||
? string.Empty : "A text slot requires a string, number, or boolean value.";
|
||||
|
||||
if (type is VisualBriefingSlotType.TIMELINE)
|
||||
return ValidateTimeline(value);
|
||||
|
||||
if (value.ValueKind is not JsonValueKind.Object)
|
||||
return "A table slot requires an object with columns and rows.";
|
||||
|
||||
@ -100,4 +109,34 @@ internal static class VisualBriefingSlotTypes
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks the fixed timeline content shape used by the deterministic compiler.
|
||||
/// </summary>
|
||||
/// <param name="value">The timeline slot value returned by the model.</param>
|
||||
/// <returns>A short reason when the value does not match, otherwise an empty string.</returns>
|
||||
private static string ValidateTimeline(JsonElement value)
|
||||
{
|
||||
if (value.ValueKind is not JsonValueKind.Object || value.EnumerateObject().Select(property => property.Name).ToArray() is not ["items"])
|
||||
return "A timeline slot requires exactly one items array.";
|
||||
|
||||
var items = value.GetProperty("items");
|
||||
if (items.ValueKind is not JsonValueKind.Array || items.GetArrayLength() < 2)
|
||||
return "A timeline requires at least two ordered items.";
|
||||
|
||||
foreach (var item in items.EnumerateArray())
|
||||
{
|
||||
if (item.ValueKind is not JsonValueKind.Object)
|
||||
return "Every timeline item requires period, title, and description strings.";
|
||||
|
||||
var properties = item.EnumerateObject().Select(property => property.Name).ToArray();
|
||||
if (properties.Length != 3 || !properties.ToHashSet(StringComparer.Ordinal).SetEquals(["period", "title", "description"]))
|
||||
return "Every timeline item requires exactly period, title, and description.";
|
||||
|
||||
if (properties.Any(property => item.GetProperty(property).ValueKind is not JsonValueKind.String || string.IsNullOrWhiteSpace(item.GetProperty(property).GetString())))
|
||||
return "Every timeline period, title, and description requires a non-empty string.";
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AIStudio.Assistants.VisualBriefing;
|
||||
|
||||
/// <summary>
|
||||
/// Selects the desktop presentation direction of a chronological timeline.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<VisualBriefingTimelineOrientation>))]
|
||||
public enum VisualBriefingTimelineOrientation
|
||||
{
|
||||
/// <summary>Places timeline items along a horizontal track on sufficiently wide screens.</summary>
|
||||
HORIZONTAL,
|
||||
|
||||
/// <summary>Places timeline items along a vertical track.</summary>
|
||||
VERTICAL,
|
||||
}
|
||||
@ -197,9 +197,12 @@ internal static partial class VisualBriefingValidation
|
||||
item.EvidenceIds.Count == 0 ||
|
||||
item.EvidenceIds.Distinct(StringComparer.Ordinal).Count() != item.EvidenceIds.Count ||
|
||||
item.EvidenceIds.Any(id => !evidenceIds.Contains(id)) ||
|
||||
!HasValidSlotPattern(item)))
|
||||
!HasValidSlotPattern(item) ||
|
||||
item.Kind is VisualBriefingComponentKind.TIMELINE &&
|
||||
item.TimelineOrientation is not (VisualBriefingTimelineOrientation.HORIZONTAL or VisualBriefingTimelineOrientation.VERTICAL) ||
|
||||
item.Kind is not VisualBriefingComponentKind.TIMELINE && item.TimelineOrientation is not null))
|
||||
return Invalid(
|
||||
"Every component must reference valid evidence and use the exact slot roles for its kind.",
|
||||
"Every component must reference valid evidence and use the exact slots and orientation for its kind.",
|
||||
VisualBriefingValidationRule.REFERENCE_INVALID);
|
||||
|
||||
var plannedAssetIds = components
|
||||
@ -674,6 +677,7 @@ internal static partial class VisualBriefingValidation
|
||||
VisualBriefingComponentKind.TABS => roles is [VisualBriefingSlotRole.TITLE, VisualBriefingSlotRole.SUMMARY, _, ..] && roles.Skip(2).All(role => role is VisualBriefingSlotRole.PANEL),
|
||||
VisualBriefingComponentKind.ACCORDION => roles.SequenceEqual([VisualBriefingSlotRole.TITLE, VisualBriefingSlotRole.BODY]),
|
||||
VisualBriefingComponentKind.SIMULATION => roles is [VisualBriefingSlotRole.TITLE, VisualBriefingSlotRole.SUMMARY, _, ..] && roles.Skip(2).All(role => role is VisualBriefingSlotRole.RESULT),
|
||||
VisualBriefingComponentKind.TIMELINE => roles.SequenceEqual([VisualBriefingSlotRole.TITLE, VisualBriefingSlotRole.SUMMARY, VisualBriefingSlotRole.TIMELINE_DATA]),
|
||||
|
||||
_ => false,
|
||||
};
|
||||
|
||||
@ -12,13 +12,13 @@ public static class VisualBriefingVersions
|
||||
public const int MANIFEST = 1;
|
||||
|
||||
/// <summary>Gets the canonical data schema version.</summary>
|
||||
public const int SCHEMA = 1;
|
||||
public const int SCHEMA = 2;
|
||||
|
||||
/// <summary>
|
||||
/// 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 = 1;
|
||||
public const int COMPILER = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the embedded AI Studio runtime bundle version. Increment this for changes to the
|
||||
@ -33,17 +33,17 @@ public static class VisualBriefingVersions
|
||||
public const int BUILD = 1;
|
||||
|
||||
/// <summary>Gets the immutable intermediate-artifact contract version.</summary>
|
||||
public const int INTERMEDIATE_ARTIFACT = 1;
|
||||
public const int INTERMEDIATE_ARTIFACT = 2;
|
||||
|
||||
/// <summary>Gets the evidence-agent response contract version.</summary>
|
||||
public const int EVIDENCE_CONTRACT = 1;
|
||||
public const int EVIDENCE_CONTRACT = 2;
|
||||
|
||||
/// <summary>Gets the plan-agent response contract version.</summary>
|
||||
public const int PLAN_CONTRACT = 1;
|
||||
public const int PLAN_CONTRACT = 2;
|
||||
|
||||
/// <summary>Gets the content-agent response contract version.</summary>
|
||||
public const int CONTENT_CONTRACT = 1;
|
||||
public const int CONTENT_CONTRACT = 2;
|
||||
|
||||
/// <summary>Gets the design-agent response contract version.</summary>
|
||||
public const int DESIGN_CONTRACT = 1;
|
||||
public const int DESIGN_CONTRACT = 2;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user