using AIStudio.Assistants.SlideBuilder; using AIStudio.Chat; using AIStudio.Settings; using ComponentKind = AIStudio.Tools.Components; using ProviderSettings = AIStudio.Settings.Provider; namespace AIStudio.Assistants.VisualBriefing; /// /// Holds the editable state of one visual briefing while the user works on it. /// /// /// This is the single source of truth for the briefing editor. It exists because the editor cannot /// bind to directly: that type stores the provider, model, /// and profile as identifiers, while the UI binds whole and /// objects. Keeping one draft object means saving, restoring, and change /// detection all read the same fields instead of three hand-maintained lists. /// public sealed class VisualBriefingEditorState { /// Gets or sets the briefing name. public string Name { get; set; } = string.Empty; /// Gets or sets the optional author. public string Author { get; set; } = string.Empty; /// Gets or sets the selected provider and model. public ProviderSettings Provider { get; set; } = ProviderSettings.NONE; /// Gets or sets the selected profile. public Profile Profile { get; set; } = Profile.NO_PROFILE; /// Gets or sets the current scope or change instruction. public string Instruction { get; set; } = string.Empty; /// Gets or sets the selected target language. public CommonLanguages TargetLanguage { get; set; } = CommonLanguages.EN_US; /// Gets or sets a free-form target language. public string CustomTargetLanguage { get; set; } = string.Empty; /// Gets or sets the audience profile. public AudienceProfile AudienceProfile { get; set; } /// Gets or sets the audience age group. public AudienceAgeGroup AudienceAgeGroup { get; set; } /// Gets or sets the audience organizational level. public AudienceOrganizationalLevel AudienceOrganizationalLevel { get; set; } /// Gets or sets the audience expertise. public AudienceExpertise AudienceExpertise { get; set; } /// Gets or sets whether visible source references are requested. public bool ShowSourceReferences { get; set; } = true; /// Gets or sets whether large visual assets are optimized. public bool OptimizeImages { get; set; } = true; /// Gets or sets the selected protection level. public VisualBriefingProtectionLevel ProtectionLevel { get; set; } = VisualBriefingProtectionLevel.INTERNAL; /// Gets or sets the free-form protection level. public string CustomProtectionLevel { get; set; } = string.Empty; /// Gets or sets the source-material attachments. public HashSet SourceMaterial { get; set; } = []; /// Gets or sets the visual-asset attachments. public HashSet VisualAssets { get; set; } = []; /// /// Creates the editor state for a stored briefing. /// /// The manifest to read. /// The settings used to resolve the stored provider and profile. /// The editor state for the briefing. public static VisualBriefingEditorState FromManifest(VisualBriefingManifest briefing, SettingsManager settingsManager) => new() { Name = briefing.Name, Author = briefing.Author, Instruction = briefing.Settings.Instruction, TargetLanguage = briefing.Settings.TargetLanguage, CustomTargetLanguage = briefing.Settings.CustomTargetLanguage, AudienceProfile = briefing.Settings.AudienceProfile, AudienceAgeGroup = briefing.Settings.AudienceAgeGroup, AudienceOrganizationalLevel = briefing.Settings.AudienceOrganizationalLevel, AudienceExpertise = briefing.Settings.AudienceExpertise, ShowSourceReferences = briefing.Settings.ShowSourceReferences, OptimizeImages = briefing.Settings.OptimizeImages, ProtectionLevel = briefing.Settings.ProtectionLevel, CustomProtectionLevel = briefing.Settings.CustomProtectionLevel, Provider = ResolveProvider(briefing, settingsManager), Profile = settingsManager.GetProfileById(briefing.Settings.ProfileId), SourceMaterial = [ .. briefing.Sources .Where(source => source.Kind is VisualBriefingSourceKind.SOURCE_MATERIAL) .Select(source => FileAttachment.FromPath(source.Path)) ], VisualAssets = [ .. briefing.Sources .Where(source => source.Kind is VisualBriefingSourceKind.VISUAL_ASSET) .Select(source => FileAttachment.FromPath(source.Path)) ], }; /// /// Resolves the provider a stored briefing refers to. /// /// /// /// A briefing stores its provider and model as two separate ids, and both must still match: when /// the user changed the model of that provider, the stored combination no longer exists and the /// editor starts without a provider. /// /// /// The resolved provider is additionally checked against the minimum confidence level of the /// visual briefing assistant. This matters because the confidence settings may have become /// stricter since the briefing was stored: the user may have lowered the confidence of that /// provider, or may now enforce a global minimum. Without this check, opening an old briefing /// would silently restore a provider the user no longer trusts, bypassing the filtering that /// the provider dropdown applies. Note that the component minimum already covers the enforced /// global minimum as well. /// /// /// The manifest to read. /// The settings used to resolve the provider. /// The stored provider, or when it is unavailable or no longer trusted. private static ProviderSettings ResolveProvider(VisualBriefingManifest briefing, SettingsManager settingsManager) { var storedProvider = settingsManager.GetProviderById(briefing.Settings.ProviderId); if (storedProvider == ProviderSettings.NONE) return ProviderSettings.NONE; if (storedProvider.Model.Id != briefing.Settings.ModelId) return ProviderSettings.NONE; if (!settingsManager.IsProviderConfident(storedProvider, ComponentKind.VISUAL_BRIEFING_ASSISTANT)) return ProviderSettings.NONE; return storedProvider; } /// /// Creates the persisted settings for this editor state. /// /// The settings to store. public VisualBriefingLocalSettings ToSettings() => new() { ProviderId = this.Provider.Id, ModelId = this.Provider.Model.Id, ProfileId = this.Profile.Id, TargetLanguage = this.TargetLanguage, CustomTargetLanguage = this.CustomTargetLanguage, AudienceProfile = this.AudienceProfile, AudienceAgeGroup = this.AudienceAgeGroup, AudienceOrganizationalLevel = this.AudienceOrganizationalLevel, AudienceExpertise = this.AudienceExpertise, ShowSourceReferences = this.ShowSourceReferences, OptimizeImages = this.OptimizeImages, Instruction = this.Instruction, ProtectionLevel = this.ProtectionLevel, CustomProtectionLevel = this.CustomProtectionLevel, }; /// /// Creates the persisted source list for this editor state. /// /// /// Source material is listed before visual assets on purpose: the store discards duplicates by /// path and keeps the first occurrence, so this order decides which kind wins when the same file /// appears in both lists. Within each kind the paths are ordered so that the same editor state /// always produces the same sequence, which is what makes change detection reliable. /// /// The sources to store, in a stable order. public IEnumerable<(string Path, VisualBriefingSourceKind Kind)> ToSources() => OrderedSources(this.SourceMaterial, VisualBriefingSourceKind.SOURCE_MATERIAL) .Concat(OrderedSources(this.VisualAssets, VisualBriefingSourceKind.VISUAL_ASSET)); /// /// Orders one attachment set into stable source entries of a single kind. /// /// The attachments to convert. /// The kind to assign. /// The ordered source entries. private static IEnumerable<(string Path, VisualBriefingSourceKind Kind)> OrderedSources(IEnumerable attachments, VisualBriefingSourceKind kind) => attachments .Select(attachment => attachment.FilePath) .Order(StringComparer.Ordinal) .Select(path => (path, kind)); }