mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-11-04 21:27:21 +00:00
Generated the JSON contracts of visual briefings and model answers at compile time (#1151)
This commit is contained in:
1 parent
24e3c22db3
commit
770c16e20a
101 files changed
+2333
-392
No files matched your search
@@ -2,12 +2,12 @@ namespace AIStudio.Assistants.Builder;
|
||||
|
||||
internal sealed class AssistantBuilderAssistantMetadata
|
||||
{
|
||||
public string Kind { get; init; } = string.Empty;
|
||||
public string Title { get; init; } = string.Empty;
|
||||
public string Description { get; init; } = string.Empty;
|
||||
public string? SystemPrompt { get; init; }
|
||||
public string? SubmitText { get; init; }
|
||||
public bool? AllowAiStudioProfiles { get; init; }
|
||||
public string[]? ToolIds { get; init; }
|
||||
public AssistantBuilderChatLaunchMetadata? Launch { get; init; }
|
||||
public string Kind { get; set; } = string.Empty;
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public string? SystemPrompt { get; set; }
|
||||
public string? SubmitText { get; set; }
|
||||
public bool? AllowAiStudioProfiles { get; set; }
|
||||
public string[]? ToolIds { get; set; }
|
||||
public AssistantBuilderChatLaunchMetadata? Launch { get; set; }
|
||||
}
|
||||
@@ -6,10 +6,10 @@ internal sealed class AssistantBuilderChatLaunchMetadata
|
||||
/// The workspace the chat is created in. A model leaves this field out for a launcher that
|
||||
/// opens a chat without a workspace, which is why it stays empty rather than null.
|
||||
/// </summary>
|
||||
public string WorkspaceName { get; init; } = string.Empty;
|
||||
public string? ProviderId { get; init; }
|
||||
public string? ProfileId { get; init; }
|
||||
public string? ChatTemplateId { get; init; }
|
||||
public string[]? DataSourceIds { get; init; }
|
||||
public string[]? ToolIds { get; init; }
|
||||
public string WorkspaceName { get; set; } = string.Empty;
|
||||
public string? ProviderId { get; set; }
|
||||
public string? ProfileId { get; set; }
|
||||
public string? ChatTemplateId { get; set; }
|
||||
public string[]? DataSourceIds { get; set; }
|
||||
public string[]? ToolIds { get; set; }
|
||||
}
|
||||
@@ -2,7 +2,7 @@ namespace AIStudio.Assistants.Builder;
|
||||
|
||||
internal sealed class AssistantBuilderPluginMetadata
|
||||
{
|
||||
public string Name { get; init; } = string.Empty;
|
||||
public string Description { get; init; } = string.Empty;
|
||||
public string[] Categories { get; init; } = [];
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public string[] Categories { get; set; } = [];
|
||||
}
|
||||
@@ -14,30 +14,31 @@ internal sealed class LauncherTextsResponse
|
||||
{
|
||||
public const string SCHEMA_VERSION_VALUE = "assistant_builder_launcher_texts_v1";
|
||||
|
||||
private static readonly JsonSerializerOptions JSON_OPTIONS = new()
|
||||
internal static readonly JsonSerializerOptions JSON_OPTIONS = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||
AllowTrailingCommas = false,
|
||||
ReadCommentHandling = JsonCommentHandling.Disallow,
|
||||
MaxDepth = 8,
|
||||
};
|
||||
TypeInfoResolver = LauncherTextsResponseJsonContext.Default,
|
||||
}.AsReadOnly();
|
||||
|
||||
public string SchemaVersion { get; init; } = string.Empty;
|
||||
public string SchemaVersion { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The plugin name, shown on the plugins page.
|
||||
/// </summary>
|
||||
public string PluginName { get; init; } = string.Empty;
|
||||
public string PluginName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The title on the tile.
|
||||
/// </summary>
|
||||
public string Title { get; init; } = string.Empty;
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The short description, used for both the plugin and the tile.
|
||||
/// </summary>
|
||||
public string Description { get; init; } = string.Empty;
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
public static bool TryParse(string modelResponse, out LauncherTextsResponse response, out LuaResponseParseError error, out string technicalDetails)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AIStudio.Assistants.Builder;
|
||||
|
||||
/// <summary>
|
||||
/// The contracts with which System.Text.Json reads the answer of a model which names a direct chat launcher, built
|
||||
/// at compile time.
|
||||
/// </summary>
|
||||
[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)]
|
||||
[JsonSerializable(typeof(LauncherTextsResponse))]
|
||||
internal sealed partial class LauncherTextsResponseJsonContext : JsonSerializerContext;
|
||||
@@ -4,13 +4,14 @@ namespace AIStudio.Assistants.Builder;
|
||||
|
||||
internal sealed partial class LuaResponse
|
||||
{
|
||||
private static readonly JsonSerializerOptions JSON_OPTIONS = new()
|
||||
internal static readonly JsonSerializerOptions JSON_OPTIONS = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||
AllowTrailingCommas = false,
|
||||
ReadCommentHandling = JsonCommentHandling.Disallow,
|
||||
MaxDepth = 32,
|
||||
};
|
||||
TypeInfoResolver = LuaResponseJsonContext.Default,
|
||||
}.AsReadOnly();
|
||||
|
||||
public static bool TryParse(string modelResponse, out LuaResponse response, out LuaResponseParseError error, out string technicalDetails)
|
||||
{
|
||||
|
||||
@@ -3,8 +3,8 @@ namespace AIStudio.Assistants.Builder;
|
||||
internal sealed partial class LuaResponse
|
||||
{
|
||||
public const string SCHEMA_VERSION_VALUE = "assistant_builder_lua_response_v2";
|
||||
public string SchemaVersion { get; init; } = string.Empty;
|
||||
public AssistantBuilderPluginMetadata? Plugin { get; init; }
|
||||
public AssistantBuilderAssistantMetadata? Assistant { get; init; }
|
||||
public string FullLua { get; init; } = string.Empty;
|
||||
public string SchemaVersion { get; set; } = string.Empty;
|
||||
public AssistantBuilderPluginMetadata? Plugin { get; set; }
|
||||
public AssistantBuilderAssistantMetadata? Assistant { get; set; }
|
||||
public string FullLua { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AIStudio.Assistants.Builder;
|
||||
|
||||
/// <summary>
|
||||
/// The contracts with which System.Text.Json reads the answer of a model which writes an assistant plugin, built at
|
||||
/// compile time.
|
||||
/// </summary>
|
||||
[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)]
|
||||
[JsonSerializable(typeof(LuaResponse))]
|
||||
internal sealed partial class LuaResponseJsonContext : JsonSerializerContext;
|
||||
@@ -13,18 +13,19 @@ namespace AIStudio.Assistants.ERI;
|
||||
/// </remarks>
|
||||
internal sealed class ERIServerFileList
|
||||
{
|
||||
private static readonly JsonSerializerOptions JSON_OPTIONS = new()
|
||||
internal static readonly JsonSerializerOptions JSON_OPTIONS = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
AllowTrailingCommas = false,
|
||||
ReadCommentHandling = JsonCommentHandling.Disallow,
|
||||
MaxDepth = 8,
|
||||
};
|
||||
TypeInfoResolver = ERIServerFileListJsonContext.Default,
|
||||
}.AsReadOnly();
|
||||
|
||||
/// <summary>
|
||||
/// The paths of the files, relative to the base directory.
|
||||
/// </summary>
|
||||
public List<string>? Files { get; init; }
|
||||
public List<string>? Files { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reads the list of files out of the answer of the model.
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AIStudio.Assistants.ERI;
|
||||
|
||||
/// <summary>
|
||||
/// The contracts with which System.Text.Json reads the files a model plans for an ERI server, built at compile time.
|
||||
/// </summary>
|
||||
[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)]
|
||||
[JsonSerializable(typeof(ERIServerFileList))]
|
||||
internal sealed partial class ERIServerFileListJsonContext : JsonSerializerContext;
|
||||
@@ -17,10 +17,11 @@ namespace AIStudio.Assistants.PromptOptimizer;
|
||||
|
||||
public partial class AssistantPromptOptimizer : AssistantBaseCore<SettingsDialogPromptOptimizer>
|
||||
{
|
||||
private static readonly JsonSerializerOptions JSON_OPTIONS = new()
|
||||
internal static readonly JsonSerializerOptions JSON_OPTIONS = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
};
|
||||
TypeInfoResolver = PromptOptimizationJsonContext.Default,
|
||||
}.AsReadOnly();
|
||||
|
||||
[Inject]
|
||||
private IDialogService DialogService { get; init; } = null!;
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AIStudio.Assistants.PromptOptimizer;
|
||||
|
||||
/// <summary>
|
||||
/// The contracts with which System.Text.Json reads the answer of the prompt optimizer, built at compile time.
|
||||
/// </summary>
|
||||
[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)]
|
||||
[JsonSerializable(typeof(PromptOptimizationResult))]
|
||||
internal sealed partial class PromptOptimizationJsonContext : JsonSerializerContext;
|
||||
@@ -15,10 +15,12 @@ namespace AIStudio.Assistants.SWOTAnalysis;
|
||||
/// </remarks>
|
||||
internal static class SwotAnalysisModeration
|
||||
{
|
||||
private static readonly JsonSerializerOptions JSON_OPTIONS = new()
|
||||
// Internal rather than private, so that a test can check the JSON the moderator gets:
|
||||
internal static readonly JsonSerializerOptions JSON_OPTIONS = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
};
|
||||
TypeInfoResolver = SwotModerationJsonContext.Default,
|
||||
}.AsReadOnly();
|
||||
|
||||
public static string BuildSystemPrompt(string outputLanguageInstruction) =>
|
||||
$"""
|
||||
@@ -138,16 +140,9 @@ internal static class SwotAnalysisModeration
|
||||
};
|
||||
}
|
||||
|
||||
private static object CreateCategoryPayload(SwotCategoryAnalysisResult analysis, string factorIdPrefix) => new
|
||||
{
|
||||
empty_message = analysis.EmptyMessage,
|
||||
findings = analysis.Findings.Select((finding, index) => new
|
||||
{
|
||||
factor_id = $"{factorIdPrefix}{index + 1}",
|
||||
summary = finding.Summary,
|
||||
explanation = finding.Explanation,
|
||||
}),
|
||||
};
|
||||
private static SwotModerationCategoryInput CreateCategoryPayload(SwotCategoryAnalysisResult analysis, string factorIdPrefix) => new(
|
||||
analysis.EmptyMessage,
|
||||
[.. analysis.Findings.Select((finding, index) => new SwotModerationFindingInput($"{factorIdPrefix}{index + 1}", finding.Summary, finding.Explanation))]);
|
||||
|
||||
private static SwotCategory CreateCategory(SwotModeratedCategory category) => new()
|
||||
{
|
||||
|
||||
@@ -8,13 +8,14 @@ namespace AIStudio.Assistants.SWOTAnalysis;
|
||||
/// </summary>
|
||||
internal static class SwotModelAnswer
|
||||
{
|
||||
private static readonly JsonSerializerOptions JSON_OPTIONS = new()
|
||||
internal static readonly JsonSerializerOptions JSON_OPTIONS = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
AllowTrailingCommas = true,
|
||||
ReadCommentHandling = JsonCommentHandling.Skip,
|
||||
RespectNullableAnnotations = true,
|
||||
};
|
||||
TypeInfoResolver = SwotModelAnswerJsonContext.Default,
|
||||
}.AsReadOnly();
|
||||
|
||||
/// <summary>
|
||||
/// Reads the JSON object of a model answer into the given type, without checking its content.
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AIStudio.Assistants.SWOTAnalysis;
|
||||
|
||||
/// <summary>
|
||||
/// The contracts with which System.Text.Json reads the answers of the models which work on a SWOT analysis, built at
|
||||
/// compile time.
|
||||
/// </summary>
|
||||
[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)]
|
||||
[JsonSerializable(typeof(SwotCategoryAnalysisResult))]
|
||||
[JsonSerializable(typeof(SwotModerationResult))]
|
||||
internal sealed partial class SwotModelAnswerJsonContext : JsonSerializerContext;
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AIStudio.Assistants.SWOTAnalysis;
|
||||
|
||||
/// <summary>
|
||||
/// The preliminary result of one category, as the moderator gets it.
|
||||
/// </summary>
|
||||
/// <param name="EmptyMessage">Why the category has no findings, or an empty string.</param>
|
||||
/// <param name="Findings">The findings of the category, numbered for reference.</param>
|
||||
internal sealed record SwotModerationCategoryInput(
|
||||
[property: JsonPropertyName("empty_message")] string EmptyMessage,
|
||||
[property: JsonPropertyName("findings")] IReadOnlyList<SwotModerationFindingInput> Findings);
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AIStudio.Assistants.SWOTAnalysis;
|
||||
|
||||
/// <summary>
|
||||
/// A preliminary finding, as the moderator gets it.
|
||||
/// </summary>
|
||||
/// <param name="FactorId">The ID of the finding, for reference only: the moderator numbers the final findings anew.</param>
|
||||
/// <param name="Summary">The summary of the finding.</param>
|
||||
/// <param name="Explanation">The explanation of the finding.</param>
|
||||
internal sealed record SwotModerationFindingInput(
|
||||
[property: JsonPropertyName("factor_id")] string FactorId,
|
||||
[property: JsonPropertyName("summary")] string Summary,
|
||||
[property: JsonPropertyName("explanation")] string Explanation);
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AIStudio.Assistants.SWOTAnalysis;
|
||||
|
||||
/// <summary>
|
||||
/// The contracts with which System.Text.Json writes the preliminary results the moderator of a SWOT analysis gets,
|
||||
/// built at compile time.
|
||||
/// </summary>
|
||||
[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)]
|
||||
[JsonSerializable(typeof(Dictionary<string, SwotModerationCategoryInput>))]
|
||||
internal sealed partial class SwotModerationJsonContext : JsonSerializerContext;
|
||||
+14
-13
@@ -370,7 +370,10 @@ public sealed partial class VisualBriefingArtifactService
|
||||
/// <summary>
|
||||
/// Defines <c>AddProtectedArtifactData</c> for the visual briefing feature.
|
||||
/// </summary>
|
||||
private static JsonElement AddProtectedArtifactData(VisualBriefingManifest manifest, VisualBriefingRevisionRequest request)
|
||||
/// <remarks>
|
||||
/// Internal, so that a test can check the JSON it writes.
|
||||
/// </remarks>
|
||||
internal static JsonElement AddProtectedArtifactData(VisualBriefingManifest manifest, VisualBriefingRevisionRequest request)
|
||||
{
|
||||
var source = request.Data;
|
||||
var dictionary = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(source.GetRawText(), JSON_OPTIONS) ?? [];
|
||||
@@ -378,26 +381,24 @@ public sealed partial class VisualBriefingArtifactService
|
||||
dictionary.Remove("footerTemplates");
|
||||
dictionary.Remove("protectionLabel");
|
||||
dictionary.Remove("_mwai");
|
||||
dictionary["_mwai"] = JsonSerializer.SerializeToElement(new
|
||||
{
|
||||
schemaVersion = VisualBriefingVersions.SCHEMA,
|
||||
runtimeVersion = VisualBriefingVersions.RUNTIME,
|
||||
aiStudioVersion = Assembly.GetExecutingAssembly().GetCustomAttribute<MetaDataAttribute>()?.Version ?? "unknown",
|
||||
assets = request.EmbeddedAssets ?? new Dictionary<string, string>(StringComparer.Ordinal),
|
||||
assetMetadata = (request.AssetPlan ?? []).ToDictionary(
|
||||
dictionary["_mwai"] = JsonSerializer.SerializeToElement(new VisualBriefingProtectedData(
|
||||
VisualBriefingVersions.SCHEMA,
|
||||
VisualBriefingVersions.RUNTIME,
|
||||
Assembly.GetExecutingAssembly().GetCustomAttribute<MetaDataAttribute>()?.Version ?? "unknown",
|
||||
request.EmbeddedAssets ?? new Dictionary<string, string>(StringComparer.Ordinal),
|
||||
(request.AssetPlan ?? []).ToDictionary(
|
||||
asset => asset.AssetId,
|
||||
asset => new { asset.Description, asset.AltText },
|
||||
asset => new VisualBriefingAssetMetadata(asset.Description, asset.AltText),
|
||||
StringComparer.Ordinal),
|
||||
footer = BuildFooter(manifest, request),
|
||||
}, JSON_OPTIONS);
|
||||
|
||||
BuildFooter(manifest, request)), JSON_OPTIONS);
|
||||
|
||||
return JsonSerializer.SerializeToElement(dictionary, JSON_OPTIONS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines <c>BuildFooter</c> for the visual briefing feature.
|
||||
/// </summary>
|
||||
private static object BuildFooter(VisualBriefingManifest manifest, VisualBriefingRevisionRequest request)
|
||||
private static Dictionary<string, string> BuildFooter(VisualBriefingManifest manifest, VisualBriefingRevisionRequest request)
|
||||
{
|
||||
var source = request.ExportMetadataSource;
|
||||
var protectionLevel = source?.ProtectionLevel ?? manifest.Settings.ProtectionLevel;
|
||||
|
||||
+1
-1
@@ -204,7 +204,7 @@ public sealed partial class VisualBriefingArtifactService
|
||||
return context;
|
||||
|
||||
if (path is "$index")
|
||||
return JsonSerializer.SerializeToElement(0);
|
||||
return JsonSerializer.SerializeToElement(0, JSON_OPTIONS);
|
||||
|
||||
if (path.StartsWith(".", StringComparison.Ordinal))
|
||||
return GetDataAtPath(context, path[1..]);
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace AIStudio.Assistants.VisualBriefing;
|
||||
|
||||
/// <summary>
|
||||
/// What the protected data of an exported briefing tells about one of its pictures.
|
||||
/// </summary>
|
||||
/// <param name="Description">What the picture shows.</param>
|
||||
/// <param name="AltText">The text for people who cannot see the picture.</param>
|
||||
internal sealed record VisualBriefingAssetMetadata(string Description, string AltText);
|
||||
@@ -13,17 +13,17 @@ public sealed class VisualBriefingAssetPlanItem
|
||||
/// Gets or sets the stable visual asset identifier.
|
||||
/// </summary>
|
||||
[JsonRequired]
|
||||
public string AssetId { get; init; } = string.Empty;
|
||||
public string AssetId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the model's visual description for presentation decisions.
|
||||
/// </summary>
|
||||
[JsonRequired]
|
||||
public string Description { get; init; } = string.Empty;
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the target-language text alternative.
|
||||
/// </summary>
|
||||
[JsonRequired]
|
||||
public string AltText { get; init; } = string.Empty;
|
||||
public string AltText { get; set; } = string.Empty;
|
||||
}
|
||||
+5
-6
@@ -382,16 +382,15 @@ public partial class VisualBriefingAssistant
|
||||
/// The fingerprint is serialized from exactly the values that SaveCurrentAsync
|
||||
/// hands to the store. That is deliberate: a handwritten field list would silently stop
|
||||
/// auto-saving whenever a new setting is added and someone forgets to list it here. Sources are
|
||||
/// projected into a named shape because <c>System.Text.Json</c> ignores tuple fields and would
|
||||
/// projected into a named record because <c>System.Text.Json</c> ignores tuple fields and would
|
||||
/// otherwise serialize every source list into the same empty object.
|
||||
/// </remarks>
|
||||
/// <returns>The fingerprint of the current editor state.</returns>
|
||||
private string BuildPersistenceFingerprint() => JsonSerializer.Serialize(
|
||||
new
|
||||
{
|
||||
new VisualBriefingPersistenceFingerprint(
|
||||
this.editor.Name,
|
||||
this.editor.Author,
|
||||
Settings = this.editor.ToSettings(),
|
||||
Sources = this.editor.ToSources().Select(source => new { source.Path, source.Kind }).ToArray(),
|
||||
}, VisualBriefingJson.Canonical);
|
||||
this.editor.ToSettings(),
|
||||
[.. this.editor.ToSources().Select(source => new VisualBriefingSourceFingerprint(source.Path, source.Kind))]),
|
||||
VisualBriefingJson.Canonical);
|
||||
}
|
||||
+1
-15
@@ -121,21 +121,7 @@ internal sealed partial class VisualBriefingBuildOrchestrator
|
||||
var validationDataProperties = compiled.Data.EnumerateObject()
|
||||
.ToDictionary(property => property.Name, property => property.Value.Clone(), StringComparer.Ordinal);
|
||||
|
||||
validationDataProperties["_mwai"] = JsonSerializer.SerializeToElement(new
|
||||
{
|
||||
schemaVersion = VisualBriefingVersions.SCHEMA,
|
||||
runtimeVersion = VisualBriefingVersions.RUNTIME,
|
||||
aiStudioVersion = "validation",
|
||||
assets = content.AssetPlan.ToDictionary(asset => asset.AssetId, _ => "data:image/png;base64,AA==", StringComparer.Ordinal),
|
||||
footer = new
|
||||
{
|
||||
createdWith = "validation",
|
||||
models = "validation",
|
||||
createdAt = "validation",
|
||||
authors = "validation",
|
||||
protection = "validation",
|
||||
},
|
||||
}, VisualBriefingJson.Canonical);
|
||||
validationDataProperties["_mwai"] = JsonSerializer.SerializeToElement(VisualBriefingProtectedData.ForValidation(content.AssetPlan), VisualBriefingJson.Canonical);
|
||||
|
||||
VisualBriefingCompilerInvariant.Guard(
|
||||
VisualBriefingBuildStage.COMPILATION,
|
||||
|
||||
@@ -8,12 +8,12 @@ public sealed class VisualBriefingBuildRecord
|
||||
/// <summary>
|
||||
/// Gets or sets the build-record schema version.
|
||||
/// </summary>
|
||||
public int BuildVersion { get; init; } = VisualBriefingVersions.BUILD;
|
||||
public int BuildVersion { get; set; } = VisualBriefingVersions.BUILD;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the build identifier.
|
||||
/// </summary>
|
||||
public Guid BuildId { get; init; }
|
||||
public Guid BuildId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the operation identifier shown in diagnostics and logs.
|
||||
@@ -23,17 +23,17 @@ public sealed class VisualBriefingBuildRecord
|
||||
/// <summary>
|
||||
/// Gets or sets the owning briefing identifier.
|
||||
/// </summary>
|
||||
public Guid BriefingId { get; init; }
|
||||
public Guid BriefingId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the requested edit mode.
|
||||
/// </summary>
|
||||
public VisualBriefingEditMode Mode { get; init; }
|
||||
public VisualBriefingEditMode Mode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the parent revision identifier.
|
||||
/// </summary>
|
||||
public Guid? ParentRevisionId { get; init; }
|
||||
public Guid? ParentRevisionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the local revision instruction used for recovery.
|
||||
@@ -48,7 +48,7 @@ public sealed class VisualBriefingBuildRecord
|
||||
/// <summary>
|
||||
/// Gets or sets durable stage progress.
|
||||
/// </summary>
|
||||
public List<VisualBriefingBuildStageRecord> Stages { get; init; } = [];
|
||||
public List<VisualBriefingBuildStageRecord> Stages { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the content artifact identifier.
|
||||
@@ -83,47 +83,47 @@ public sealed class VisualBriefingBuildRecord
|
||||
/// <summary>
|
||||
/// Gets or sets the complete safe input fingerprint.
|
||||
/// </summary>
|
||||
public string InputFingerprint { get; init; } = string.Empty;
|
||||
public string InputFingerprint { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the source and transcript fingerprint.
|
||||
/// </summary>
|
||||
public string SourceFingerprint { get; init; } = string.Empty;
|
||||
public string SourceFingerprint { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the content prompt contract version.
|
||||
/// </summary>
|
||||
public int ContentContractVersion { get; init; } = VisualBriefingVersions.CONTENT_CONTRACT;
|
||||
public int ContentContractVersion { get; set; } = VisualBriefingVersions.CONTENT_CONTRACT;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the evidence prompt contract version.
|
||||
/// </summary>
|
||||
public int EvidenceContractVersion { get; init; } = VisualBriefingVersions.EVIDENCE_CONTRACT;
|
||||
public int EvidenceContractVersion { get; set; } = VisualBriefingVersions.EVIDENCE_CONTRACT;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the plan prompt contract version.
|
||||
/// </summary>
|
||||
public int PlanContractVersion { get; init; } = VisualBriefingVersions.PLAN_CONTRACT;
|
||||
public int PlanContractVersion { get; set; } = VisualBriefingVersions.PLAN_CONTRACT;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the design prompt contract version.
|
||||
/// </summary>
|
||||
public int DesignContractVersion { get; init; } = VisualBriefingVersions.DESIGN_CONTRACT;
|
||||
public int DesignContractVersion { get; set; } = VisualBriefingVersions.DESIGN_CONTRACT;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the selected provider family.
|
||||
/// </summary>
|
||||
public string ProviderFamily { get; init; } = string.Empty;
|
||||
public string ProviderFamily { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the selected model name.
|
||||
/// </summary>
|
||||
public string Model { get; init; } = string.Empty;
|
||||
public string Model { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the build creation time.
|
||||
/// </summary>
|
||||
public DateTimeOffset CreatedAtUtc { get; init; }
|
||||
public DateTimeOffset CreatedAtUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the most recent build update time.
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AIStudio.Assistants.VisualBriefing;
|
||||
|
||||
/// <summary>
|
||||
/// The contracts with which System.Text.Json writes and reads the canonical JSON of visual briefings, built at
|
||||
/// compile time.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Much of what these contracts write goes into the hashes of stored briefings, see VisualBriefingJson.Canonical.
|
||||
/// The hashes of the artifacts are generic: every list they hash needs a contract of its own. A test fails for a type
|
||||
/// which is missing here.
|
||||
/// </remarks>
|
||||
[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)]
|
||||
|
||||
// The header and the data of an exported briefing, and the build which the progress view shows:
|
||||
[JsonSerializable(typeof(VisualBriefingExportManifest))]
|
||||
[JsonSerializable(typeof(VisualBriefingBuildRecord))]
|
||||
[JsonSerializable(typeof(VisualBriefingCompiledData))]
|
||||
[JsonSerializable(typeof(VisualBriefingContentData))]
|
||||
[JsonSerializable(typeof(VisualBriefingInteractionData))]
|
||||
[JsonSerializable(typeof(VisualBriefingProtectedData))]
|
||||
[JsonSerializable(typeof(Dictionary<string, JsonElement>))]
|
||||
[JsonSerializable(typeof(JsonElement))]
|
||||
[JsonSerializable(typeof(JsonObject))]
|
||||
[JsonSerializable(typeof(string))]
|
||||
[JsonSerializable(typeof(int))]
|
||||
|
||||
// The payload hashes of the artifacts:
|
||||
[JsonSerializable(typeof(List<VisualBriefingEvidenceFact>))]
|
||||
[JsonSerializable(typeof(List<VisualBriefingEvidenceMetric>))]
|
||||
[JsonSerializable(typeof(List<VisualBriefingEvidenceTable>))]
|
||||
[JsonSerializable(typeof(List<VisualBriefingSourceCoverage>))]
|
||||
[JsonSerializable(typeof(List<VisualBriefingAssetPlanItem>))]
|
||||
[JsonSerializable(typeof(List<VisualBriefingPlanSection>))]
|
||||
[JsonSerializable(typeof(List<VisualBriefingSlotValue>))]
|
||||
[JsonSerializable(typeof(List<VisualBriefingChartSpec>))]
|
||||
[JsonSerializable(typeof(List<VisualBriefingControlSpec>))]
|
||||
[JsonSerializable(typeof(List<VisualBriefingFormulaSpec>))]
|
||||
[JsonSerializable(typeof(Dictionary<string, string>))]
|
||||
[JsonSerializable(typeof(Dictionary<string, List<string>>))]
|
||||
[JsonSerializable(typeof(VisualBriefingLayoutNode))]
|
||||
|
||||
// The prompts of the stages:
|
||||
[JsonSerializable(typeof(IEnumerable<VisualBriefingSourcePrompt>))]
|
||||
[JsonSerializable(typeof(string[]))]
|
||||
[JsonSerializable(typeof(VisualBriefingRequiredSlot[]))]
|
||||
[JsonSerializable(typeof(VisualBriefingControlRequirement[]))]
|
||||
[JsonSerializable(typeof(VisualBriefingEvidencePrompt))]
|
||||
[JsonSerializable(typeof(VisualBriefingParentDesign))]
|
||||
|
||||
// The answers of the models:
|
||||
[JsonSerializable(typeof(VisualBriefingEvidenceResponse))]
|
||||
[JsonSerializable(typeof(VisualBriefingPlanResponse))]
|
||||
[JsonSerializable(typeof(VisualBriefingContentResponse))]
|
||||
[JsonSerializable(typeof(VisualBriefingDesignResponse))]
|
||||
|
||||
// The fingerprint which decides whether the editor holds unsaved changes:
|
||||
[JsonSerializable(typeof(VisualBriefingPersistenceFingerprint))]
|
||||
internal sealed partial class VisualBriefingCanonicalJsonContext : JsonSerializerContext;
|
||||
@@ -1,10 +1,16 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
namespace AIStudio.Assistants.VisualBriefing;
|
||||
|
||||
/// <summary>
|
||||
/// Turns a validated chart specification into a branded chart-library option object.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The option object is built as JSON nodes rather than as types of its own: it follows the options of Apache
|
||||
/// ECharts, which differ for every kind of chart, and nothing in AI Studio reads it again. The order of its
|
||||
/// properties is part of the compiled data, which gets hashed.
|
||||
/// </remarks>
|
||||
internal static class VisualBriefingChartCompiler
|
||||
{
|
||||
/// <summary>
|
||||
@@ -14,114 +20,116 @@ internal static class VisualBriefingChartCompiler
|
||||
/// <returns>The branded chart option.</returns>
|
||||
internal static JsonElement Compile(VisualBriefingChartSpec chart)
|
||||
{
|
||||
object series = chart.Kind switch
|
||||
var series = chart.Kind switch
|
||||
{
|
||||
VisualBriefingChartKind.PIE or VisualBriefingChartKind.DONUT =>
|
||||
chart.Categories.Select((category, index) => new
|
||||
VisualBriefingChartKind.PIE or VisualBriefingChartKind.DONUT => new JsonArray(
|
||||
[
|
||||
.. chart.Categories.Select((category, index) => new JsonObject
|
||||
{
|
||||
name = category,
|
||||
value = chart.Series[0].Values[index],
|
||||
}).ToArray(),
|
||||
["name"] = category,
|
||||
["value"] = chart.Series[0].Values[index],
|
||||
}),
|
||||
]),
|
||||
|
||||
VisualBriefingChartKind.RADAR => chart.Series.Select(item => new
|
||||
{
|
||||
name = item.Name,
|
||||
type = "radar",
|
||||
data = new[]
|
||||
VisualBriefingChartKind.RADAR => new JsonArray(
|
||||
[
|
||||
.. chart.Series.Select(item => new JsonObject
|
||||
{
|
||||
new
|
||||
["name"] = item.Name,
|
||||
["type"] = "radar",
|
||||
["data"] = new JsonArray(new JsonObject
|
||||
{
|
||||
value = item.Values,
|
||||
name = item.Name,
|
||||
},
|
||||
},
|
||||
}).ToArray(),
|
||||
["value"] = Numbers(item.Values),
|
||||
["name"] = item.Name,
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
|
||||
_ => chart.Series.Select(item => new
|
||||
{
|
||||
name = item.Name,
|
||||
type = SeriesType(chart.Kind),
|
||||
stack = chart.Kind is VisualBriefingChartKind.STACKED_BAR ? "total" : null,
|
||||
areaStyle = chart.Kind is VisualBriefingChartKind.AREA ? new { opacity = 0.18 } : null,
|
||||
smooth = chart.Kind is VisualBriefingChartKind.LINE or VisualBriefingChartKind.AREA,
|
||||
showSymbol = chart.Kind is VisualBriefingChartKind.SCATTER,
|
||||
symbolSize = chart.Kind is VisualBriefingChartKind.SCATTER ? 10 : 6,
|
||||
itemStyle = chart.Kind is VisualBriefingChartKind.BAR or VisualBriefingChartKind.STACKED_BAR
|
||||
? new { borderRadius = new[] { 6, 6, 0, 0 } } : null,
|
||||
data = item.Values,
|
||||
}).ToArray(),
|
||||
_ => new JsonArray(
|
||||
[
|
||||
.. chart.Series.Select(item => new JsonObject
|
||||
{
|
||||
["name"] = item.Name,
|
||||
["type"] = SeriesType(chart.Kind),
|
||||
["stack"] = chart.Kind is VisualBriefingChartKind.STACKED_BAR ? "total" : null,
|
||||
["areaStyle"] = chart.Kind is VisualBriefingChartKind.AREA ? new JsonObject { ["opacity"] = 0.18 } : null,
|
||||
["smooth"] = chart.Kind is VisualBriefingChartKind.LINE or VisualBriefingChartKind.AREA,
|
||||
["showSymbol"] = chart.Kind is VisualBriefingChartKind.SCATTER,
|
||||
["symbolSize"] = chart.Kind is VisualBriefingChartKind.SCATTER ? 10 : 6,
|
||||
["itemStyle"] = chart.Kind is VisualBriefingChartKind.BAR or VisualBriefingChartKind.STACKED_BAR
|
||||
? new JsonObject { ["borderRadius"] = new JsonArray(6, 6, 0, 0) } : null,
|
||||
["data"] = Numbers(item.Values),
|
||||
}),
|
||||
]),
|
||||
};
|
||||
|
||||
var option = new
|
||||
var isCircular = chart.Kind is VisualBriefingChartKind.PIE or VisualBriefingChartKind.DONUT;
|
||||
var hasAxes = chart.Kind is not (VisualBriefingChartKind.PIE or VisualBriefingChartKind.DONUT or VisualBriefingChartKind.RADAR);
|
||||
var option = new JsonObject
|
||||
{
|
||||
color = new[] { "#236A50", "#F2D264", "#79AE90", "#C97857", "#4E7894", "#9B6B8F" },
|
||||
backgroundColor = "transparent",
|
||||
textStyle = new
|
||||
["color"] = Texts("#236A50", "#F2D264", "#79AE90", "#C97857", "#4E7894", "#9B6B8F"),
|
||||
["backgroundColor"] = "transparent",
|
||||
["textStyle"] = new JsonObject
|
||||
{
|
||||
color = "#172A24",
|
||||
fontFamily = "system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif",
|
||||
["color"] = "#172A24",
|
||||
["fontFamily"] = "system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif",
|
||||
},
|
||||
|
||||
tooltip = new
|
||||
["tooltip"] = new JsonObject
|
||||
{
|
||||
trigger = chart.Kind is VisualBriefingChartKind.PIE or VisualBriefingChartKind.DONUT ? "item" : "axis",
|
||||
borderColor = "#D6E2DC",
|
||||
backgroundColor = "#FFFEFA",
|
||||
textStyle = new { color = "#172A24" },
|
||||
["trigger"] = isCircular ? "item" : "axis",
|
||||
["borderColor"] = "#D6E2DC",
|
||||
["backgroundColor"] = "#FFFEFA",
|
||||
["textStyle"] = new JsonObject { ["color"] = "#172A24" },
|
||||
},
|
||||
|
||||
legend = new { show = true, top = 0, textStyle = new { color = "#4F635B" } },
|
||||
grid = chart.Kind is VisualBriefingChartKind.PIE or VisualBriefingChartKind.DONUT or VisualBriefingChartKind.RADAR
|
||||
? null
|
||||
: new { left = 8, right = 16, top = 48, bottom = 8, containLabel = true },
|
||||
["legend"] = new JsonObject { ["show"] = true, ["top"] = 0, ["textStyle"] = new JsonObject { ["color"] = "#4F635B" } },
|
||||
["grid"] = hasAxes
|
||||
? new JsonObject { ["left"] = 8, ["right"] = 16, ["top"] = 48, ["bottom"] = 8, ["containLabel"] = true }
|
||||
: null,
|
||||
|
||||
xAxis = chart.Kind is VisualBriefingChartKind.PIE or VisualBriefingChartKind.DONUT or VisualBriefingChartKind.RADAR
|
||||
? null
|
||||
: new
|
||||
["xAxis"] = hasAxes
|
||||
? new JsonObject
|
||||
{
|
||||
type = "category",
|
||||
data = chart.Categories,
|
||||
axisLine = new { lineStyle = new { color = "#B8C9C0" } },
|
||||
axisTick = new { show = false },
|
||||
axisLabel = new { color = "#5E7169" },
|
||||
},
|
||||
|
||||
yAxis = chart.Kind is VisualBriefingChartKind.PIE or VisualBriefingChartKind.DONUT or VisualBriefingChartKind.RADAR
|
||||
? null
|
||||
: new
|
||||
{
|
||||
type = "value",
|
||||
axisLine = new { show = false },
|
||||
axisTick = new { show = false },
|
||||
axisLabel = new { color = "#5E7169" },
|
||||
splitLine = new { lineStyle = new { color = "#E1EAE5" } },
|
||||
},
|
||||
|
||||
radar = chart.Kind is VisualBriefingChartKind.RADAR
|
||||
? new
|
||||
{
|
||||
indicator = chart.Categories.Select(name => new { name }).ToArray(),
|
||||
splitArea = new { areaStyle = new { color = new[] { "#FFFEFA", "#EAF1EC" } } },
|
||||
axisName = new { color = "#5E7169" },
|
||||
splitLine = new { lineStyle = new { color = "#B8C9C0" } },
|
||||
["type"] = "category",
|
||||
["data"] = Texts(chart.Categories),
|
||||
["axisLine"] = new JsonObject { ["lineStyle"] = new JsonObject { ["color"] = "#B8C9C0" } },
|
||||
["axisTick"] = new JsonObject { ["show"] = false },
|
||||
["axisLabel"] = new JsonObject { ["color"] = "#5E7169" },
|
||||
}
|
||||
: null,
|
||||
|
||||
series = chart.Kind is VisualBriefingChartKind.PIE or VisualBriefingChartKind.DONUT
|
||||
? new[]
|
||||
["yAxis"] = hasAxes
|
||||
? new JsonObject
|
||||
{
|
||||
new
|
||||
{
|
||||
type = "pie",
|
||||
radius = chart.Kind is VisualBriefingChartKind.DONUT
|
||||
? new[] { "45%", "70%" }
|
||||
: new[] { "0%", "70%" },
|
||||
padAngle = 2,
|
||||
itemStyle = new { borderColor = "#FFFEFA", borderWidth = 2, borderRadius = 5 },
|
||||
label = new { color = "#4F635B" },
|
||||
data = series,
|
||||
},
|
||||
["type"] = "value",
|
||||
["axisLine"] = new JsonObject { ["show"] = false },
|
||||
["axisTick"] = new JsonObject { ["show"] = false },
|
||||
["axisLabel"] = new JsonObject { ["color"] = "#5E7169" },
|
||||
["splitLine"] = new JsonObject { ["lineStyle"] = new JsonObject { ["color"] = "#E1EAE5" } },
|
||||
}
|
||||
: null,
|
||||
|
||||
["radar"] = chart.Kind is VisualBriefingChartKind.RADAR
|
||||
? new JsonObject
|
||||
{
|
||||
["indicator"] = new JsonArray([.. chart.Categories.Select(name => new JsonObject { ["name"] = name })]),
|
||||
["splitArea"] = new JsonObject { ["areaStyle"] = new JsonObject { ["color"] = Texts("#FFFEFA", "#EAF1EC") } },
|
||||
["axisName"] = new JsonObject { ["color"] = "#5E7169" },
|
||||
["splitLine"] = new JsonObject { ["lineStyle"] = new JsonObject { ["color"] = "#B8C9C0" } },
|
||||
}
|
||||
: null,
|
||||
|
||||
["series"] = isCircular
|
||||
? new JsonArray(new JsonObject
|
||||
{
|
||||
["type"] = "pie",
|
||||
["radius"] = chart.Kind is VisualBriefingChartKind.DONUT ? Texts("45%", "70%") : Texts("0%", "70%"),
|
||||
["padAngle"] = 2,
|
||||
["itemStyle"] = new JsonObject { ["borderColor"] = "#FFFEFA", ["borderWidth"] = 2, ["borderRadius"] = 5 },
|
||||
["label"] = new JsonObject { ["color"] = "#4F635B" },
|
||||
["data"] = series,
|
||||
})
|
||||
: series,
|
||||
};
|
||||
|
||||
@@ -141,4 +149,8 @@ internal static class VisualBriefingChartCompiler
|
||||
VisualBriefingChartKind.RADAR => "radar",
|
||||
_ => "line",
|
||||
};
|
||||
|
||||
private static JsonArray Texts(params IEnumerable<string> texts) => new([.. texts.Select(text => JsonValue.Create(text))]);
|
||||
|
||||
private static JsonArray Numbers(IEnumerable<decimal> numbers) => new([.. numbers.Select(number => JsonValue.Create(number))]);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace AIStudio.Assistants.VisualBriefing;
|
||||
|
||||
/// <summary>
|
||||
/// The data of a compiled briefing, which its template binds to.
|
||||
/// </summary>
|
||||
/// <param name="Slots">The content of each slot, by its ID.</param>
|
||||
/// <param name="Charts">The chart-library option object of each chart, by the ID of its component.</param>
|
||||
/// <param name="Interactions">The controls, their state, and the formulas of the briefing.</param>
|
||||
/// <param name="Accessibility">The text for people who cannot see a component, by the ID of the component.</param>
|
||||
/// <param name="SourceReferences">The sources of each component, by the ID of the component.</param>
|
||||
/// <param name="Labels">The fixed labels which the runtime of the briefing shows.</param>
|
||||
internal sealed record VisualBriefingCompiledData(
|
||||
Dictionary<string, JsonElement> Slots,
|
||||
Dictionary<string, JsonElement> Charts,
|
||||
JsonElement Interactions,
|
||||
Dictionary<string, string> Accessibility,
|
||||
Dictionary<string, List<string>> SourceReferences,
|
||||
VisualBriefingLabels Labels);
|
||||
@@ -0,0 +1,37 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace AIStudio.Assistants.VisualBriefing;
|
||||
|
||||
/// <summary>
|
||||
/// The data of a content artifact, as the content stage keeps it along with the artifact.
|
||||
/// </summary>
|
||||
/// <param name="Slots">The content of each slot, by its ID.</param>
|
||||
/// <param name="Charts">The validated chart specifications.</param>
|
||||
/// <param name="Controls">The validated interaction controls.</param>
|
||||
/// <param name="Formulas">The validated formula specifications.</param>
|
||||
/// <param name="Accessibility">The text for people who cannot see a component, by the ID of the component.</param>
|
||||
/// <param name="SourceReferences">The sources of each component, by the ID of the component.</param>
|
||||
/// <param name="Labels">The fixed labels which the runtime of the briefing shows.</param>
|
||||
internal sealed record VisualBriefingContentData(
|
||||
Dictionary<string, JsonElement> Slots,
|
||||
List<VisualBriefingChartSpec> Charts,
|
||||
List<VisualBriefingControlSpec> Controls,
|
||||
List<VisualBriefingFormulaSpec> Formulas,
|
||||
Dictionary<string, string> Accessibility,
|
||||
Dictionary<string, List<string>> SourceReferences,
|
||||
VisualBriefingLabels Labels)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gathers the data of a content artifact.
|
||||
/// </summary>
|
||||
/// <param name="artifact">The content artifact.</param>
|
||||
/// <returns>The data of the artifact.</returns>
|
||||
internal static VisualBriefingContentData Of(VisualBriefingContentArtifact artifact) => new(
|
||||
artifact.Slots.ToDictionary(slot => slot.SlotId, slot => slot.Value, StringComparer.Ordinal),
|
||||
artifact.Charts,
|
||||
artifact.Controls,
|
||||
artifact.Formulas,
|
||||
artifact.AccessibilityTexts,
|
||||
artifact.SourceReferences,
|
||||
VisualBriefingLabels.Of(artifact.ResetLabel));
|
||||
}
|
||||
@@ -52,20 +52,7 @@ internal sealed class VisualBriefingContentStage(StructuredLlmStageRunner stageR
|
||||
artifact.SourceCoverage = evidence.SourceCoverage;
|
||||
artifact.StructuralSignature = plan.StructuralSignature;
|
||||
artifact.Model = VisualBriefingModelNames.ExportLabel(provider);
|
||||
artifact.Data = JsonSerializer.SerializeToElement(new
|
||||
{
|
||||
slots = artifact.Slots.ToDictionary(slot => slot.SlotId, slot => slot.Value, StringComparer.Ordinal),
|
||||
charts = artifact.Charts,
|
||||
controls = artifact.Controls,
|
||||
formulas = artifact.Formulas,
|
||||
accessibility = artifact.AccessibilityTexts,
|
||||
sourceReferences = artifact.SourceReferences,
|
||||
labels = new
|
||||
{
|
||||
reset = artifact.ResetLabel,
|
||||
brand = "MindWork AI Studio",
|
||||
},
|
||||
}, VisualBriefingJson.Canonical);
|
||||
artifact.Data = JsonSerializer.SerializeToElement(VisualBriefingContentData.Of(artifact), VisualBriefingJson.Canonical);
|
||||
|
||||
artifact.PayloadHash = VisualBriefingPayloadHash.ForContent(artifact.Slots, artifact.Charts, artifact.Controls, artifact.Formulas, artifact.AccessibilityTexts,
|
||||
artifact.SourceReferences, artifact.ResetLabel, artifact.SourceCoverage, artifact.AssetPlan, artifact.StructuralSignature);
|
||||
@@ -107,7 +94,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();
|
||||
@@ -116,21 +104,10 @@ internal sealed class VisualBriefingContentStage(StructuredLlmStageRunner stageR
|
||||
var requiredSlots = plan.Sections
|
||||
.SelectMany(section => new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
SlotId = section.TitleSlotId,
|
||||
Role = VisualBriefingSlotRole.TITLE,
|
||||
Type = VisualBriefingSlotType.TEXT,
|
||||
},
|
||||
|
||||
new
|
||||
{
|
||||
SlotId = section.SummarySlotId,
|
||||
Role = VisualBriefingSlotRole.SUMMARY,
|
||||
Type = VisualBriefingSlotType.TEXT,
|
||||
},
|
||||
}.Concat(section.Components.SelectMany(component => component.Slots.Select(slot => new { slot.SlotId, slot.Role, Type = VisualBriefingSlotTypes.Expected(slot), }
|
||||
)))).ToArray();
|
||||
new VisualBriefingRequiredSlot(section.TitleSlotId, VisualBriefingSlotRole.TITLE, VisualBriefingSlotType.TEXT),
|
||||
new VisualBriefingRequiredSlot(section.SummarySlotId, VisualBriefingSlotRole.SUMMARY, VisualBriefingSlotType.TEXT),
|
||||
}.Concat(section.Components.SelectMany(component => component.Slots.Select(slot => new VisualBriefingRequiredSlot(slot.SlotId, slot.Role, VisualBriefingSlotTypes.Expected(slot))))))
|
||||
.ToArray();
|
||||
|
||||
var chartComponentIds = components
|
||||
.Where(component => component.Kind is VisualBriefingComponentKind.CHART)
|
||||
@@ -140,21 +117,20 @@ internal sealed class VisualBriefingContentStage(StructuredLlmStageRunner stageR
|
||||
// Filterable tables are absent here: AI Studio derives their controls from the table data:
|
||||
var controlRequirements = components
|
||||
.Where(component => component.Kind is VisualBriefingComponentKind.TABS or VisualBriefingComponentKind.SIMULATION)
|
||||
.Select(component => new
|
||||
{
|
||||
.Select(component => new VisualBriefingControlRequirement(
|
||||
component.ComponentId,
|
||||
component.Kind,
|
||||
|
||||
PanelSlotIds = component.Slots
|
||||
|
||||
PanelSlotIds: component.Slots
|
||||
.Where(slot => slot.Role is VisualBriefingSlotRole.PANEL)
|
||||
.Select(slot => slot.SlotId)
|
||||
.ToArray(),
|
||||
|
||||
ResultSlotIds = component.Slots
|
||||
|
||||
ResultSlotIds: component.Slots
|
||||
.Where(slot => slot.Role is VisualBriefingSlotRole.RESULT)
|
||||
.Select(slot => slot.SlotId)
|
||||
.ToArray(),
|
||||
}).ToArray();
|
||||
.ToArray()))
|
||||
.ToArray();
|
||||
|
||||
return $"""
|
||||
Target language: {manifest.Settings.TargetLanguage.PromptGeneralPurpose(manifest.Settings.CustomTargetLanguage)}
|
||||
@@ -166,7 +142,7 @@ internal sealed class VisualBriefingContentStage(StructuredLlmStageRunner stageR
|
||||
Exact chart component IDs, each to receive exactly one chart: {JsonSerializer.Serialize(chartComponentIds, VisualBriefingJson.Canonical)}
|
||||
Exact control and formula requirements, no controls for any other component: {JsonSerializer.Serialize(controlRequirements, VisualBriefingJson.Canonical)}
|
||||
Plan: {JsonSerializer.Serialize(plan.Sections, VisualBriefingJson.Canonical)}
|
||||
Evidence: {JsonSerializer.Serialize(new { evidence.Facts, evidence.Metrics, evidence.Tables, evidence.AssetPlan }, VisualBriefingJson.Canonical)}
|
||||
Evidence: {JsonSerializer.Serialize(VisualBriefingEvidencePrompt.Of(evidence), VisualBriefingJson.Canonical)}
|
||||
""";
|
||||
}
|
||||
|
||||
@@ -233,21 +209,7 @@ internal sealed class VisualBriefingContentStage(StructuredLlmStageRunner stageR
|
||||
var compiled = VisualBriefingCompilerInvariant.Guard(VisualBriefingBuildStage.CONTENT, () => VisualBriefingLayoutCompiler.Compile(plan, projection, layout, VisualBriefingDesignProfile.EDITORIAL));
|
||||
var data = compiled.Data.EnumerateObject().ToDictionary(property => property.Name, property => property.Value.Clone(), StringComparer.Ordinal);
|
||||
|
||||
data["_mwai"] = JsonSerializer.SerializeToElement(new
|
||||
{
|
||||
schemaVersion = VisualBriefingVersions.SCHEMA,
|
||||
runtimeVersion = VisualBriefingVersions.RUNTIME,
|
||||
aiStudioVersion = "validation",
|
||||
assets = evidence.AssetPlan.ToDictionary(asset => asset.AssetId, _ => "data:image/png;base64,AA==", StringComparer.Ordinal),
|
||||
footer = new
|
||||
{
|
||||
createdWith = "validation",
|
||||
models = "validation",
|
||||
createdAt = "validation",
|
||||
authors = "validation",
|
||||
protection = "validation",
|
||||
},
|
||||
}, VisualBriefingJson.Canonical);
|
||||
data["_mwai"] = JsonSerializer.SerializeToElement(VisualBriefingProtectedData.ForValidation(evidence.AssetPlan), VisualBriefingJson.Canonical);
|
||||
|
||||
var validationData = JsonSerializer.SerializeToElement(data, VisualBriefingJson.Canonical);
|
||||
VisualBriefingCompilerInvariant.Guard(VisualBriefingBuildStage.CONTENT,
|
||||
|
||||
@@ -11,9 +11,9 @@ public sealed class VisualBriefingControlOption
|
||||
{
|
||||
/// <summary>Gets or sets the stored option value.</summary>
|
||||
[JsonRequired]
|
||||
public string Value { get; init; } = string.Empty;
|
||||
public string Value { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Gets or sets the visible option label.</summary>
|
||||
[JsonRequired]
|
||||
public string Label { get; init; } = string.Empty;
|
||||
public string Label { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace AIStudio.Assistants.VisualBriefing;
|
||||
|
||||
/// <summary>
|
||||
/// A component for which the content stage asks its model to write controls and formulas.
|
||||
/// </summary>
|
||||
/// <param name="ComponentId">The ID of the component.</param>
|
||||
/// <param name="Kind">The kind of the component.</param>
|
||||
/// <param name="PanelSlotIds">The IDs of the panel slots of the component, in their order.</param>
|
||||
/// <param name="ResultSlotIds">The IDs of the result slots of the component, in their order.</param>
|
||||
internal sealed record VisualBriefingControlRequirement(string ComponentId, VisualBriefingComponentKind Kind, string[] PanelSlotIds, string[] ResultSlotIds);
|
||||
@@ -12,21 +12,21 @@ public sealed class VisualBriefingControlSpec
|
||||
{
|
||||
/// <summary>Gets or sets the globally unique control identifier.</summary>
|
||||
[JsonRequired]
|
||||
public string ControlId { get; init; } = string.Empty;
|
||||
public string ControlId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Gets or sets the owning component identifier.</summary>
|
||||
[JsonRequired]
|
||||
public string ComponentId { get; init; } = string.Empty;
|
||||
public string ComponentId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Gets or sets the control kind.</summary>
|
||||
[JsonRequired]
|
||||
public VisualBriefingControlKind Kind { get; init; }
|
||||
public VisualBriefingControlKind Kind { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the deterministic initial value.</summary>
|
||||
[JsonRequired]
|
||||
public JsonElement InitialValue { get; init; }
|
||||
public JsonElement InitialValue { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the selectable options.</summary>
|
||||
[JsonRequired]
|
||||
public List<VisualBriefingControlOption> Options { get; init; } = [];
|
||||
public List<VisualBriefingControlOption> Options { get; set; } = [];
|
||||
}
|
||||
@@ -15,29 +15,29 @@ public sealed class VisualBriefingEvidenceArtifact
|
||||
public int ContractVersion { get; set; } = VisualBriefingVersions.EVIDENCE_CONTRACT;
|
||||
|
||||
/// <summary>Gets or sets the immutable artifact identifier.</summary>
|
||||
public Guid ArtifactId { get; init; }
|
||||
public Guid ArtifactId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the artifact creation time.</summary>
|
||||
public DateTimeOffset CreatedAtUtc { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the hash of the artifact payload.</summary>
|
||||
public string PayloadHash { get; init; } = string.Empty;
|
||||
public string PayloadHash { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Gets or sets the extracted factual statements.</summary>
|
||||
public List<VisualBriefingEvidenceFact> Facts { get; init; } = [];
|
||||
public List<VisualBriefingEvidenceFact> Facts { get; set; } = [];
|
||||
|
||||
/// <summary>Gets or sets the extracted numeric metrics.</summary>
|
||||
public List<VisualBriefingEvidenceMetric> Metrics { get; init; } = [];
|
||||
public List<VisualBriefingEvidenceMetric> Metrics { get; set; } = [];
|
||||
|
||||
/// <summary>Gets or sets the extracted tables.</summary>
|
||||
public List<VisualBriefingEvidenceTable> Tables { get; init; } = [];
|
||||
public List<VisualBriefingEvidenceTable> Tables { get; set; } = [];
|
||||
|
||||
/// <summary>Gets or sets source coverage.</summary>
|
||||
public List<VisualBriefingSourceCoverage> SourceCoverage { get; init; } = [];
|
||||
public List<VisualBriefingSourceCoverage> SourceCoverage { get; set; } = [];
|
||||
|
||||
/// <summary>Gets or sets the visual asset plan.</summary>
|
||||
public List<VisualBriefingAssetPlanItem> AssetPlan { get; init; } = [];
|
||||
public List<VisualBriefingAssetPlanItem> AssetPlan { get; set; } = [];
|
||||
|
||||
/// <summary>Gets or sets the contributing model name.</summary>
|
||||
public string Model { get; init; } = string.Empty;
|
||||
public string Model { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace AIStudio.Assistants.VisualBriefing;
|
||||
|
||||
/// <summary>
|
||||
/// The evidence which the plan and content stages show their models.
|
||||
/// </summary>
|
||||
/// <param name="Facts">The facts of the evidence.</param>
|
||||
/// <param name="Metrics">The metrics of the evidence.</param>
|
||||
/// <param name="Tables">The tables of the evidence.</param>
|
||||
/// <param name="AssetPlan">The planned pictures of the briefing.</param>
|
||||
internal sealed record VisualBriefingEvidencePrompt(
|
||||
List<VisualBriefingEvidenceFact> Facts,
|
||||
List<VisualBriefingEvidenceMetric> Metrics,
|
||||
List<VisualBriefingEvidenceTable> Tables,
|
||||
List<VisualBriefingAssetPlanItem> AssetPlan)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gathers what a model gets to see of an evidence artifact.
|
||||
/// </summary>
|
||||
/// <param name="evidence">The evidence artifact.</param>
|
||||
/// <returns>The evidence for the prompt.</returns>
|
||||
internal static VisualBriefingEvidencePrompt Of(VisualBriefingEvidenceArtifact evidence) => new(evidence.Facts, evidence.Metrics, evidence.Tables, evidence.AssetPlan);
|
||||
}
|
||||
@@ -96,18 +96,17 @@ 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:
|
||||
var handles = VisualBriefingSourceHandles.Map(manifest);
|
||||
var sources = handles.Select(item => new
|
||||
{
|
||||
sourceId = item.Handle,
|
||||
var sources = handles.Select(item => new VisualBriefingSourcePrompt(
|
||||
item.Handle,
|
||||
item.Source.Kind,
|
||||
assetId = string.IsNullOrWhiteSpace(item.Source.AssetId) ? null : item.Source.AssetId,
|
||||
name = Path.GetFileName(item.Source.Path),
|
||||
});
|
||||
string.IsNullOrWhiteSpace(item.Source.AssetId) ? null : item.Source.AssetId,
|
||||
Path.GetFileName(item.Source.Path)));
|
||||
|
||||
var transcripts = handles
|
||||
.Where(item => preparedSources.Transcripts.ContainsKey(item.Source.SourceId))
|
||||
|
||||
@@ -11,102 +11,102 @@ public sealed class VisualBriefingExportManifest
|
||||
/// <summary>
|
||||
/// Defines <c>ArtifactVersion</c> for the visual briefing feature.
|
||||
/// </summary>
|
||||
public int ArtifactVersion { get; init; } = VisualBriefingVersions.ARTIFACT;
|
||||
public int ArtifactVersion { get; set; } = VisualBriefingVersions.ARTIFACT;
|
||||
|
||||
/// <summary>
|
||||
/// Defines <c>SchemaVersion</c> for the visual briefing feature.
|
||||
/// </summary>
|
||||
public int SchemaVersion { get; init; } = VisualBriefingVersions.SCHEMA;
|
||||
public int SchemaVersion { get; set; } = VisualBriefingVersions.SCHEMA;
|
||||
|
||||
/// <summary>
|
||||
/// Defines <c>RuntimeVersion</c> for the visual briefing feature.
|
||||
/// </summary>
|
||||
public int RuntimeVersion { get; init; } = VisualBriefingVersions.RUNTIME;
|
||||
public int RuntimeVersion { get; set; } = VisualBriefingVersions.RUNTIME;
|
||||
|
||||
/// <summary>
|
||||
/// Defines <c>BriefingId</c> for the visual briefing feature.
|
||||
/// </summary>
|
||||
public Guid BriefingId { get; init; }
|
||||
public Guid BriefingId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines <c>RevisionId</c> for the visual briefing feature.
|
||||
/// </summary>
|
||||
public Guid RevisionId { get; init; }
|
||||
public Guid RevisionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines <c>ParentRevisionId</c> for the visual briefing feature.
|
||||
/// </summary>
|
||||
public Guid? ParentRevisionId { get; init; }
|
||||
public Guid? ParentRevisionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines <c>Name</c> for the visual briefing feature.
|
||||
/// </summary>
|
||||
public string Name { get; init; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Defines <c>Author</c> for the visual briefing feature.
|
||||
/// </summary>
|
||||
public string Author { get; init; } = string.Empty;
|
||||
public string Author { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Defines <c>CreatedAtUtc</c> for the visual briefing feature.
|
||||
/// </summary>
|
||||
public DateTimeOffset CreatedAtUtc { get; init; }
|
||||
public DateTimeOffset CreatedAtUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines <c>TargetLanguage</c> for the visual briefing feature.
|
||||
/// </summary>
|
||||
public CommonLanguages TargetLanguage { get; init; }
|
||||
public CommonLanguages TargetLanguage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines <c>CustomTargetLanguage</c> for the visual briefing feature.
|
||||
/// </summary>
|
||||
public string CustomTargetLanguage { get; init; } = string.Empty;
|
||||
public string CustomTargetLanguage { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Defines <c>AudienceProfile</c> for the visual briefing feature.
|
||||
/// </summary>
|
||||
public AudienceProfile AudienceProfile { get; init; }
|
||||
public AudienceProfile AudienceProfile { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines <c>AudienceAgeGroup</c> for the visual briefing feature.
|
||||
/// </summary>
|
||||
public AudienceAgeGroup AudienceAgeGroup { get; init; }
|
||||
public AudienceAgeGroup AudienceAgeGroup { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines <c>AudienceOrganizationalLevel</c> for the visual briefing feature.
|
||||
/// </summary>
|
||||
public AudienceOrganizationalLevel AudienceOrganizationalLevel { get; init; }
|
||||
public AudienceOrganizationalLevel AudienceOrganizationalLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines <c>AudienceExpertise</c> for the visual briefing feature.
|
||||
/// </summary>
|
||||
public AudienceExpertise AudienceExpertise { get; init; }
|
||||
public AudienceExpertise AudienceExpertise { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines <c>ShowSourceReferences</c> for the visual briefing feature.
|
||||
/// </summary>
|
||||
public bool ShowSourceReferences { get; init; }
|
||||
public bool ShowSourceReferences { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines <c>ProtectionLevel</c> for the visual briefing feature.
|
||||
/// </summary>
|
||||
public VisualBriefingProtectionLevel ProtectionLevel { get; init; }
|
||||
public VisualBriefingProtectionLevel ProtectionLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines <c>CustomProtectionLevel</c> for the visual briefing feature.
|
||||
/// </summary>
|
||||
public string CustomProtectionLevel { get; init; } = string.Empty;
|
||||
public string CustomProtectionLevel { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Defines <c>AIStudioVersion</c> for the visual briefing feature.
|
||||
/// </summary>
|
||||
public string AIStudioVersion { get; init; } = string.Empty;
|
||||
public string AIStudioVersion { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Defines <c>RuntimeAIStudioVersion</c> for the visual briefing feature.
|
||||
/// </summary>
|
||||
public string RuntimeAIStudioVersion { get; init; } = string.Empty;
|
||||
public string RuntimeAIStudioVersion { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the SHA-256 hash of the complete standalone HTML document.
|
||||
|
||||
+1
-6
@@ -27,12 +27,7 @@ internal static class VisualBriefingInteractionCompiler
|
||||
formula => formula.Formula,
|
||||
StringComparer.Ordinal);
|
||||
|
||||
return JsonSerializer.SerializeToElement(new
|
||||
{
|
||||
controls,
|
||||
state,
|
||||
formulas = formulaMap,
|
||||
}, VisualBriefingJson.Canonical);
|
||||
return JsonSerializer.SerializeToElement(new VisualBriefingInteractionData(controls, state, formulaMap), VisualBriefingJson.Canonical);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace AIStudio.Assistants.VisualBriefing;
|
||||
|
||||
/// <summary>
|
||||
/// The interactions in the data of a briefing: its controls, their state, and the formulas which depend on them.
|
||||
/// </summary>
|
||||
/// <param name="Controls">The validated interaction controls.</param>
|
||||
/// <param name="State">The initial value of each control, by its ID.</param>
|
||||
/// <param name="Formulas">The formula of each result slot, by the ID of the slot.</param>
|
||||
internal sealed record VisualBriefingInteractionData(
|
||||
IReadOnlyList<VisualBriefingControlSpec> Controls,
|
||||
Dictionary<string, JsonElement> State,
|
||||
Dictionary<string, VisualBriefingFormulaNode> Formulas);
|
||||
@@ -28,7 +28,9 @@ internal static class VisualBriefingJson
|
||||
/// and a mismatch makes the store discard the stored artifact. Any change here — a converter, a
|
||||
/// naming policy, an encoder — therefore invalidates every briefing that was ever written, which
|
||||
/// surfaces as a failed integrity check rather than as a build error. This is why enums stay numeric
|
||||
/// here even though the persisted manifest writes their member names.
|
||||
/// here even though the persisted manifest writes their member names. Unlike other options of the app,
|
||||
/// these are not made read-only right here, because MWAIS0010 wants nothing but the object creation;
|
||||
/// System.Text.Json locks them with their first use anyway.
|
||||
/// </remarks>
|
||||
[CanonicalJsonConfiguration]
|
||||
internal static JsonSerializerOptions Canonical { get; } = new()
|
||||
@@ -38,6 +40,7 @@ internal static class VisualBriefingJson
|
||||
WriteIndented = false,
|
||||
Encoder = JavaScriptEncoder.Default,
|
||||
UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow,
|
||||
TypeInfoResolver = VisualBriefingCanonicalJsonContext.Default,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -52,7 +55,7 @@ internal static class VisualBriefingJson
|
||||
/// defined outside the feature, such as the target language and the audience enums. Reading accepts
|
||||
/// numbers as well, so manifests written before this distinction existed keep loading.
|
||||
/// </remarks>
|
||||
internal static JsonSerializerOptions Persistence { get; } = new()
|
||||
internal static JsonSerializerOptions Persistence { get; } = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
PropertyNameCaseInsensitive = false,
|
||||
@@ -60,5 +63,6 @@ internal static class VisualBriefingJson
|
||||
Encoder = JavaScriptEncoder.Default,
|
||||
UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow,
|
||||
Converters = { new JsonStringEnumConverter() },
|
||||
};
|
||||
TypeInfoResolver = VisualBriefingPersistenceJsonContext.Default,
|
||||
}.AsReadOnly();
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace AIStudio.Assistants.VisualBriefing;
|
||||
|
||||
/// <summary>
|
||||
/// The fixed labels in the data of a briefing, which its runtime shows.
|
||||
/// </summary>
|
||||
/// <param name="Reset">The label of the buttons which reset a simulation.</param>
|
||||
/// <param name="Brand">The name of the app which created the briefing.</param>
|
||||
internal sealed record VisualBriefingLabels(string Reset, string Brand)
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates the labels of a briefing.
|
||||
/// </summary>
|
||||
/// <param name="resetLabel">The label of the buttons which reset a simulation.</param>
|
||||
/// <returns>The labels.</returns>
|
||||
internal static VisualBriefingLabels Of(string resetLabel) => new(resetLabel, "MindWork AI Studio");
|
||||
}
|
||||
@@ -49,19 +49,9 @@ internal sealed class VisualBriefingLayoutCompiler
|
||||
|
||||
var interactions = VisualBriefingInteractionCompiler.Compile(content.Controls, content.Formulas);
|
||||
|
||||
var data = JsonSerializer.SerializeToElement(new
|
||||
{
|
||||
slots,
|
||||
charts = chartOptions,
|
||||
interactions,
|
||||
accessibility = content.AccessibilityTexts,
|
||||
sourceReferences = content.SourceReferences,
|
||||
labels = new
|
||||
{
|
||||
reset = content.ResetLabel,
|
||||
brand = "MindWork AI Studio",
|
||||
},
|
||||
}, VisualBriefingJson.Canonical);
|
||||
var data = JsonSerializer.SerializeToElement(
|
||||
new VisualBriefingCompiledData(slots, chartOptions, interactions, content.AccessibilityTexts, content.SourceReferences, VisualBriefingLabels.Of(content.ResetLabel)),
|
||||
VisualBriefingJson.Canonical);
|
||||
|
||||
var html = CompileNode(layout, sections, components, content, true);
|
||||
var css = CompileCss(profile, layout);
|
||||
|
||||
@@ -11,23 +11,23 @@ public sealed class VisualBriefingLayoutNode
|
||||
{
|
||||
/// <summary>Gets or sets the globally unique layout node identifier.</summary>
|
||||
[JsonRequired]
|
||||
public string NodeId { get; init; } = string.Empty;
|
||||
public string NodeId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Gets or sets the node kind.</summary>
|
||||
[JsonRequired]
|
||||
public VisualBriefingLayoutNodeKind Kind { get; init; }
|
||||
public VisualBriefingLayoutNodeKind Kind { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the planned section identifier for a section node.</summary>
|
||||
[JsonRequired]
|
||||
public string? SectionId { get; init; }
|
||||
public string? SectionId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the planned component identifier for a component node.</summary>
|
||||
[JsonRequired]
|
||||
public string? ComponentId { get; init; }
|
||||
public string? ComponentId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the ordered child nodes.</summary>
|
||||
[JsonRequired]
|
||||
public List<VisualBriefingLayoutNode> Children { get; init; } = [];
|
||||
public List<VisualBriefingLayoutNode> Children { get; set; } = [];
|
||||
|
||||
/// <summary>Gets or sets responsive columns for a grid node.</summary>
|
||||
[JsonRequired]
|
||||
@@ -39,7 +39,7 @@ public sealed class VisualBriefingLayoutNode
|
||||
|
||||
/// <summary>Gets or sets the explicit sibling order.</summary>
|
||||
[JsonRequired]
|
||||
public int Order { get; init; }
|
||||
public int Order { get; set; }
|
||||
|
||||
/// <summary>Gets or sets whether the node receives visual emphasis.</summary>
|
||||
[JsonRequired]
|
||||
|
||||
@@ -52,7 +52,7 @@ public sealed class VisualBriefingManifest
|
||||
/// than in the settings, which the editor replaces with every save. A briefing nothing was sent to, or one
|
||||
/// written before this property existed, demands nothing.
|
||||
/// </remarks>
|
||||
public ChatRestrictions Restrictions { get; init; } = ChatRestrictions.NONE;
|
||||
public ChatRestrictions Restrictions { get; set; } = ChatRestrictions.NONE;
|
||||
|
||||
/// <summary>
|
||||
/// Defines <c>Sources</c> for the visual briefing feature.
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace AIStudio.Assistants.VisualBriefing;
|
||||
|
||||
/// <summary>
|
||||
/// The design of the previous revision, which the presentation stage shows its model when the design changes.
|
||||
/// </summary>
|
||||
/// <param name="Layout">The layout tree of the previous revision.</param>
|
||||
/// <param name="Profile">The design profile of the previous revision.</param>
|
||||
internal sealed record VisualBriefingParentDesign(VisualBriefingLayoutNode Layout, VisualBriefingDesignProfile Profile);
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
namespace AIStudio.Assistants.VisualBriefing;
|
||||
|
||||
/// <summary>
|
||||
/// What the editor of the Visual Briefing assistant hands to the store, so that it notices any change worth saving.
|
||||
/// </summary>
|
||||
/// <param name="Name">The name of the briefing.</param>
|
||||
/// <param name="Author">The author of the briefing.</param>
|
||||
/// <param name="Settings">The settings of the briefing.</param>
|
||||
/// <param name="Sources">The sources of the briefing.</param>
|
||||
internal sealed record VisualBriefingPersistenceFingerprint(string Name, string Author, VisualBriefingLocalSettings Settings, VisualBriefingSourceFingerprint[] Sources);
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AIStudio.Assistants.VisualBriefing;
|
||||
|
||||
/// <summary>
|
||||
/// The contracts with which System.Text.Json reads and writes the files of visual briefings, built at compile time.
|
||||
/// </summary>
|
||||
[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)]
|
||||
[JsonSerializable(typeof(VisualBriefingManifest))]
|
||||
[JsonSerializable(typeof(VisualBriefingEvidenceArtifact))]
|
||||
[JsonSerializable(typeof(VisualBriefingPlanArtifact))]
|
||||
[JsonSerializable(typeof(VisualBriefingContentArtifact))]
|
||||
[JsonSerializable(typeof(VisualBriefingPresentationArtifact))]
|
||||
[JsonSerializable(typeof(VisualBriefingBuildRecord))]
|
||||
[JsonSerializable(typeof(VisualBriefingOperationDiagnostics))]
|
||||
|
||||
// The briefing selected last:
|
||||
[JsonSerializable(typeof(Guid?))]
|
||||
internal sealed partial class VisualBriefingPersistenceJsonContext : JsonSerializerContext;
|
||||
@@ -15,20 +15,20 @@ public sealed class VisualBriefingPlanArtifact
|
||||
public int ContractVersion { get; set; } = VisualBriefingVersions.PLAN_CONTRACT;
|
||||
|
||||
/// <summary>Gets or sets the immutable artifact identifier.</summary>
|
||||
public Guid ArtifactId { get; init; }
|
||||
public Guid ArtifactId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the artifact creation time.</summary>
|
||||
public DateTimeOffset CreatedAtUtc { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the hash of the artifact payload.</summary>
|
||||
public string PayloadHash { get; init; } = string.Empty;
|
||||
public string PayloadHash { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Gets or sets the ordered planned sections.</summary>
|
||||
public List<VisualBriefingPlanSection> Sections { get; init; } = [];
|
||||
public List<VisualBriefingPlanSection> Sections { get; set; } = [];
|
||||
|
||||
/// <summary>Gets or sets the canonical structural signature.</summary>
|
||||
public string StructuralSignature { get; init; } = string.Empty;
|
||||
public string StructuralSignature { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Gets or sets the contributing model name.</summary>
|
||||
public string Model { get; init; } = string.Empty;
|
||||
public string Model { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -107,10 +107,11 @@ 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}
|
||||
Evidence: {JsonSerializer.Serialize(new { evidence.Facts, evidence.Metrics, evidence.Tables, evidence.AssetPlan }, VisualBriefingJson.Canonical)}
|
||||
Evidence: {JsonSerializer.Serialize(VisualBriefingEvidencePrompt.Of(evidence), VisualBriefingJson.Canonical)}
|
||||
""";
|
||||
}
|
||||
+4
-22
@@ -127,26 +127,7 @@ internal sealed class VisualBriefingPresentationStage(StructuredLlmStageRunner s
|
||||
() => VisualBriefingLayoutCompiler.Compile(plan, content, response.Layout, response.Profile));
|
||||
|
||||
var data = compiled.Data.EnumerateObject().ToDictionary(property => property.Name, property => property.Value.Clone(), StringComparer.Ordinal);
|
||||
data["_mwai"] = JsonSerializer.SerializeToElement(new
|
||||
{
|
||||
schemaVersion = VisualBriefingVersions.SCHEMA,
|
||||
runtimeVersion = VisualBriefingVersions.RUNTIME,
|
||||
aiStudioVersion = "validation",
|
||||
|
||||
assets = content.AssetPlan.ToDictionary(
|
||||
asset => asset.AssetId,
|
||||
_ => "data:image/png;base64,AA==",
|
||||
StringComparer.Ordinal),
|
||||
|
||||
footer = new
|
||||
{
|
||||
createdWith = "validation",
|
||||
models = "validation",
|
||||
createdAt = "validation",
|
||||
authors = "validation",
|
||||
protection = "validation",
|
||||
},
|
||||
}, VisualBriefingJson.Canonical);
|
||||
data["_mwai"] = JsonSerializer.SerializeToElement(VisualBriefingProtectedData.ForValidation(content.AssetPlan), VisualBriefingJson.Canonical);
|
||||
|
||||
var validationData = JsonSerializer.SerializeToElement(data, VisualBriefingJson.Canonical);
|
||||
VisualBriefingCompilerInvariant.Guard(VisualBriefingBuildStage.DESIGN,
|
||||
@@ -181,9 +162,10 @@ 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);
|
||||
var parentJson = parent is null ? "none" : JsonSerializer.Serialize(new VisualBriefingParentDesign(parent.Layout, parent.Profile), VisualBriefingJson.Canonical);
|
||||
return $"""
|
||||
Operation: {(parent is null ? "CREATE_DESIGN" : "CHANGE_DESIGN")}
|
||||
Design instruction: {manifest.Settings.Instruction}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AIStudio.Assistants.VisualBriefing;
|
||||
|
||||
/// <summary>
|
||||
/// The data of a briefing which AI Studio writes itself and no model may change, under the key <c>_mwai</c>.
|
||||
/// </summary>
|
||||
/// <param name="SchemaVersion">The version of the schema of the briefing data.</param>
|
||||
/// <param name="RuntimeVersion">The version of the runtime script of the briefing.</param>
|
||||
/// <param name="AIStudioVersion">The version of AI Studio which created the briefing.</param>
|
||||
/// <param name="Assets">The embedded pictures, by their asset IDs.</param>
|
||||
/// <param name="AssetMetadata">What the briefing tells about each picture, or null where the data only gets validated.</param>
|
||||
/// <param name="Footer">The texts of the footer, by their keys.</param>
|
||||
internal sealed record VisualBriefingProtectedData(
|
||||
int SchemaVersion,
|
||||
int RuntimeVersion,
|
||||
string AIStudioVersion,
|
||||
IReadOnlyDictionary<string, string> Assets,
|
||||
[property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] Dictionary<string, VisualBriefingAssetMetadata>? AssetMetadata,
|
||||
Dictionary<string, string> Footer)
|
||||
{
|
||||
private const string VALIDATION_VALUE = "validation";
|
||||
|
||||
private const string VALIDATION_ASSET = "data:image/png;base64,AA==";
|
||||
|
||||
/// <summary>
|
||||
/// Creates stand-in protected data for validating a compiled briefing before it gets assembled.
|
||||
/// </summary>
|
||||
/// <param name="assetPlan">The planned pictures of the briefing.</param>
|
||||
/// <returns>The stand-in protected data.</returns>
|
||||
internal static VisualBriefingProtectedData ForValidation(IEnumerable<VisualBriefingAssetPlanItem> assetPlan) => new(
|
||||
VisualBriefingVersions.SCHEMA,
|
||||
VisualBriefingVersions.RUNTIME,
|
||||
VALIDATION_VALUE,
|
||||
assetPlan.ToDictionary(asset => asset.AssetId, _ => VALIDATION_ASSET, StringComparer.Ordinal),
|
||||
null,
|
||||
new Dictionary<string, string>(StringComparer.Ordinal)
|
||||
{
|
||||
["createdWith"] = VALIDATION_VALUE,
|
||||
["models"] = VALIDATION_VALUE,
|
||||
["createdAt"] = VALIDATION_VALUE,
|
||||
["authors"] = VALIDATION_VALUE,
|
||||
["protection"] = VALIDATION_VALUE,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace AIStudio.Assistants.VisualBriefing;
|
||||
|
||||
/// <summary>
|
||||
/// A slot which the content stage asks its model to fill, with what the slot is for.
|
||||
/// </summary>
|
||||
/// <param name="SlotId">The ID of the slot.</param>
|
||||
/// <param name="Role">The semantic role of the slot.</param>
|
||||
/// <param name="Type">The type of content the slot takes.</param>
|
||||
internal sealed record VisualBriefingRequiredSlot(string SlotId, VisualBriefingSlotRole Role, VisualBriefingSlotType Type);
|
||||
@@ -12,9 +12,9 @@ public sealed class VisualBriefingSlotValue
|
||||
{
|
||||
/// <summary>Gets or sets the planned slot identifier.</summary>
|
||||
[JsonRequired]
|
||||
public string SlotId { get; init; } = string.Empty;
|
||||
public string SlotId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Gets or sets the validated slot value.</summary>
|
||||
[JsonRequired]
|
||||
public JsonElement Value { get; init; }
|
||||
public JsonElement Value { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace AIStudio.Assistants.VisualBriefing;
|
||||
|
||||
/// <summary>
|
||||
/// A source as the fingerprint of the editor names it.
|
||||
/// </summary>
|
||||
/// <param name="Path">The path of the source.</param>
|
||||
/// <param name="Kind">The kind of the source.</param>
|
||||
internal sealed record VisualBriefingSourceFingerprint(string Path, VisualBriefingSourceKind Kind);
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace AIStudio.Assistants.VisualBriefing;
|
||||
|
||||
/// <summary>
|
||||
/// A source as the evidence stage shows it its model.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The model never sees the internal ID of a source, only its short handle.
|
||||
/// </remarks>
|
||||
/// <param name="SourceId">The short handle of the source.</param>
|
||||
/// <param name="Kind">The kind of the source.</param>
|
||||
/// <param name="AssetId">The asset ID of the source, or null when it is no picture of the briefing.</param>
|
||||
/// <param name="Name">The file name of the source.</param>
|
||||
internal sealed record VisualBriefingSourcePrompt(string SourceId, VisualBriefingSourceKind Kind, string? AssetId, string Name);
|
||||
@@ -24,7 +24,7 @@ public sealed partial class VisualBriefingStore
|
||||
try
|
||||
{
|
||||
this.LastSelectedBriefingId = briefingId;
|
||||
await WriteTextAtomicAsync(this.SelectionPath(), JsonSerializer.Serialize<Guid?>(briefingId), overwrite: true, token);
|
||||
await WriteTextAtomicAsync(this.SelectionPath(), JsonSerializer.Serialize<Guid?>(briefingId, JSON_OPTIONS), overwrite: true, token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -47,7 +47,7 @@ public sealed partial class VisualBriefingStore
|
||||
return;
|
||||
|
||||
this.LastSelectedBriefingId = null;
|
||||
await WriteTextAtomicAsync(this.SelectionPath(), JsonSerializer.Serialize<Guid?>(null), overwrite: true, token);
|
||||
await WriteTextAtomicAsync(this.SelectionPath(), JsonSerializer.Serialize<Guid?>(null, JSON_OPTIONS), overwrite: true, token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -67,7 +67,7 @@ public sealed partial class VisualBriefingStore
|
||||
try
|
||||
{
|
||||
var serialized = await File.ReadAllTextAsync(path, token);
|
||||
var selected = JsonSerializer.Deserialize<Guid?>(serialized);
|
||||
var selected = JsonSerializer.Deserialize<Guid?>(serialized, JSON_OPTIONS);
|
||||
this.LastSelectedBriefingId = selected is not null &&
|
||||
Directory.Exists(this.BriefingDirectory(selected.Value))
|
||||
? selected
|
||||
|
||||
Reference in new issue
Block a user