mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-08-11 18:52:11 +00:00
Replaced the visual briefing editor fields with a single editor state objec
This commit is contained in:
parent
070cbd01cb
commit
2510cb3c9e
@ -82,7 +82,7 @@
|
||||
@bind-IsValid="@(this.formIsValid)"
|
||||
@bind-Errors="@(this.formIssues)">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center" Wrap="Wrap.Wrap" Class="mb-3">
|
||||
<MudText Typo="Typo.h3">@this.projectName</MudText>
|
||||
<MudText Typo="Typo.h3">@this.editor.Name</MudText>
|
||||
<MudStack Row="true" Spacing="1">
|
||||
<MudButton StartIcon="@Icons.Material.Filled.DriveFileRenameOutline" OnClick="@this.RenameAsync" Disabled="@this.IsCurrentBusy">@T("Rename")</MudButton>
|
||||
<MudButton StartIcon="@Icons.Material.Filled.DeleteForever" Color="Color.Error" OnClick="@this.DeleteAsync" Disabled="@this.IsCurrentBusy">@T("Delete")</MudButton>
|
||||
@ -92,22 +92,22 @@
|
||||
<MudPaper Outlined="true" Class="pa-4 mb-4">
|
||||
<MudGrid>
|
||||
<MudItem xs="12" md="7">
|
||||
<MudTextField T="string" @bind-Text="@this.projectName" Label="@T("Briefing name")" Validation="@this.ValidateProjectName" Immediate="@true" Variant="Variant.Outlined" Disabled="@this.IsCurrentBusy"/>
|
||||
<MudTextField T="string" @bind-Text="@this.editor.Name" Label="@T("Briefing name")" Validation="@this.ValidateProjectName" Immediate="@true" Variant="Variant.Outlined" Disabled="@this.IsCurrentBusy"/>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="5">
|
||||
<MudTextField T="string" @bind-Text="@this.author" Label="@T("Author (optional)")" Variant="Variant.Outlined" Disabled="@this.IsCurrentBusy"/>
|
||||
<MudTextField T="string" @bind-Text="@this.editor.Author" Label="@T("Author (optional)")" Variant="Variant.Outlined" Disabled="@this.IsCurrentBusy"/>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
<MudTextField T="string" @bind-Text="@this.instruction" Label="@T("Briefing scope, notes, or current change instruction (optional)")" Variant="Variant.Outlined" AutoGrow="true" Lines="3" Class="mt-3" Disabled="@this.IsCurrentBusy"/>
|
||||
<MudTextField T="string" @bind-Text="@this.editor.Instruction" Label="@T("Briefing scope, notes, or current change instruction (optional)")" Variant="Variant.Outlined" AutoGrow="true" Lines="3" Class="mt-3" Disabled="@this.IsCurrentBusy"/>
|
||||
|
||||
<EnumSelection T="VisualBriefingProtectionLevel"
|
||||
NameFunc="@(level => this.ProtectionLevelName(level))"
|
||||
@bind-Value="@this.protectionLevel"
|
||||
@bind-Value="@this.editor.ProtectionLevel"
|
||||
Icon="@Icons.Material.Filled.Security"
|
||||
Label="@T("Protection level")"
|
||||
AllowOther="true"
|
||||
OtherValue="VisualBriefingProtectionLevel.OTHER"
|
||||
@bind-OtherInput="@this.customProtectionLevel"
|
||||
@bind-OtherInput="@this.editor.CustomProtectionLevel"
|
||||
ValidateOther="@this.ValidateCustomProtectionLevel"
|
||||
SelectionUpdated="@(_ => this.ScheduleFormValidation())"
|
||||
LabelOther="@T("Custom protection level")"
|
||||
@ -121,11 +121,11 @@
|
||||
<MudText Typo="Typo.body2" Class="mb-2">@T("Documents, spreadsheets, images, audio, and video are considered as source context.")</MudText>
|
||||
<AttachDocuments Name="Visual briefing source material"
|
||||
Layer="@DropLayers.ASSISTANTS"
|
||||
@bind-DocumentPaths="@this.sourceMaterial"
|
||||
@bind-DocumentPaths="@this.editor.SourceMaterial"
|
||||
OnChange="@this.SourceMaterialChangedAsync"
|
||||
CatchAllDocuments="true"
|
||||
UseSmallForm="false"
|
||||
Provider="@this.provider"
|
||||
Provider="@this.editor.Provider"
|
||||
Disabled="@this.IsCurrentBusy"/>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
@ -135,12 +135,12 @@
|
||||
<MudText Typo="Typo.body2" Class="mb-2">@T("PNG, JPEG, and WebP assets are analyzed and must appear visibly in the briefing.")</MudText>
|
||||
<AttachDocuments Name="Visual briefing visual assets"
|
||||
Layer="@DropLayers.ASSISTANTS"
|
||||
@bind-DocumentPaths="@this.visualAssets"
|
||||
@bind-DocumentPaths="@this.editor.VisualAssets"
|
||||
OnChange="@this.VisualAssetsChangedAsync"
|
||||
CatchAllDocuments="false"
|
||||
UseSmallForm="false"
|
||||
AllowedFileTypes="@(new[] { FileTypes.VISUAL_BRIEFING_IMAGE })"
|
||||
Provider="@this.provider"
|
||||
Provider="@this.editor.Provider"
|
||||
Disabled="@this.IsCurrentBusy"/>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
@ -187,15 +187,15 @@
|
||||
|
||||
<MudPaper Outlined="true" Class="pa-4 mb-4">
|
||||
<MudText Typo="Typo.h5" Class="mb-3">@T("Briefing settings")</MudText>
|
||||
<ProviderSelection @bind-ProviderSettings="@this.provider" ValidateProvider="@this.ValidateProvider" ExplicitMinimumConfidence="@this.MinimumProviderConfidence" Disabled="@this.IsCurrentBusy"/>
|
||||
<ProfileSelection @bind-CurrentProfile="@this.profile" MarginLeft="" Disabled="@this.IsCurrentBusy"/>
|
||||
<EnumSelection T="CommonLanguages" NameFunc="@(language => language.Name())" @bind-Value="@this.targetLanguage" Icon="@Icons.Material.Filled.Translate" Label="@T("Target language")" AllowOther="true" @bind-OtherInput="@this.customTargetLanguage" OtherValue="CommonLanguages.OTHER" LabelOther="@T("Custom target language")" ValidateOther="@this.ValidateCustomTargetLanguage" SelectionUpdated="@(_ => this.ScheduleFormValidation())" Disabled="@this.IsCurrentBusy"/>
|
||||
<EnumSelection T="AudienceProfile" NameFunc="@(value => value.Name())" @bind-Value="@this.audienceProfile" Label="@T("Audience profile")" Disabled="@this.IsCurrentBusy"/>
|
||||
<EnumSelection T="AudienceAgeGroup" NameFunc="@(value => value.Name())" @bind-Value="@this.audienceAgeGroup" Label="@T("Audience age group")" Disabled="@this.IsCurrentBusy"/>
|
||||
<EnumSelection T="AudienceOrganizationalLevel" NameFunc="@(value => value.Name())" @bind-Value="@this.audienceOrganizationalLevel" Label="@T("Audience organizational level")" Disabled="@this.IsCurrentBusy"/>
|
||||
<EnumSelection T="AudienceExpertise" NameFunc="@(value => value.Name())" @bind-Value="@this.audienceExpertise" Label="@T("Audience expertise")" Disabled="@this.IsCurrentBusy"/>
|
||||
<MudSwitch T="bool" @bind-Value="@this.showSourceReferences" Color="Color.Primary" Disabled="@this.IsCurrentBusy">@T("Show source references")</MudSwitch>
|
||||
<MudSwitch T="bool" @bind-Value="@this.optimizeImages" Color="Color.Primary" Disabled="@this.IsCurrentBusy">@T("Optimize large visual assets")</MudSwitch>
|
||||
<ProviderSelection @bind-ProviderSettings="@this.editor.Provider" ValidateProvider="@this.ValidateProvider" ExplicitMinimumConfidence="@this.MinimumProviderConfidence" Disabled="@this.IsCurrentBusy"/>
|
||||
<ProfileSelection @bind-CurrentProfile="@this.editor.Profile" MarginLeft="" Disabled="@this.IsCurrentBusy"/>
|
||||
<EnumSelection T="CommonLanguages" NameFunc="@(language => language.Name())" @bind-Value="@this.editor.TargetLanguage" Icon="@Icons.Material.Filled.Translate" Label="@T("Target language")" AllowOther="true" @bind-OtherInput="@this.editor.CustomTargetLanguage" OtherValue="CommonLanguages.OTHER" LabelOther="@T("Custom target language")" ValidateOther="@this.ValidateCustomTargetLanguage" SelectionUpdated="@(_ => this.ScheduleFormValidation())" Disabled="@this.IsCurrentBusy"/>
|
||||
<EnumSelection T="AudienceProfile" NameFunc="@(value => value.Name())" @bind-Value="@this.editor.AudienceProfile" Label="@T("Audience profile")" Disabled="@this.IsCurrentBusy"/>
|
||||
<EnumSelection T="AudienceAgeGroup" NameFunc="@(value => value.Name())" @bind-Value="@this.editor.AudienceAgeGroup" Label="@T("Audience age group")" Disabled="@this.IsCurrentBusy"/>
|
||||
<EnumSelection T="AudienceOrganizationalLevel" NameFunc="@(value => value.Name())" @bind-Value="@this.editor.AudienceOrganizationalLevel" Label="@T("Audience organizational level")" Disabled="@this.IsCurrentBusy"/>
|
||||
<EnumSelection T="AudienceExpertise" NameFunc="@(value => value.Name())" @bind-Value="@this.editor.AudienceExpertise" Label="@T("Audience expertise")" Disabled="@this.IsCurrentBusy"/>
|
||||
<MudSwitch T="bool" @bind-Value="@this.editor.ShowSourceReferences" Color="Color.Primary" Disabled="@this.IsCurrentBusy">@T("Show source references")</MudSwitch>
|
||||
<MudSwitch T="bool" @bind-Value="@this.editor.OptimizeImages" Color="Color.Primary" Disabled="@this.IsCurrentBusy">@T("Optimize large visual assets")</MudSwitch>
|
||||
</MudPaper>
|
||||
|
||||
<MudStack Row="true" Spacing="2" Wrap="Wrap.Wrap" Class="mb-4">
|
||||
|
||||
@ -27,10 +27,10 @@ public partial class VisualBriefingAssistant
|
||||
/// </summary>
|
||||
private bool CannotGenerate(VisualBriefingEditMode mode) =>
|
||||
this.IsCurrentBusy ||
|
||||
this.provider == ProviderSettings.NONE ||
|
||||
string.IsNullOrWhiteSpace(this.projectName) ||
|
||||
this.targetLanguage is CommonLanguages.OTHER && string.IsNullOrWhiteSpace(this.customTargetLanguage) ||
|
||||
this.protectionLevel is VisualBriefingProtectionLevel.OTHER && string.IsNullOrWhiteSpace(this.customProtectionLevel) ||
|
||||
this.editor.Provider == ProviderSettings.NONE ||
|
||||
string.IsNullOrWhiteSpace(this.editor.Name) ||
|
||||
this.editor.TargetLanguage is CommonLanguages.OTHER && string.IsNullOrWhiteSpace(this.editor.CustomTargetLanguage) ||
|
||||
this.editor.ProtectionLevel is VisualBriefingProtectionLevel.OTHER && string.IsNullOrWhiteSpace(this.editor.CustomProtectionLevel) ||
|
||||
mode is not VisualBriefingEditMode.CHANGE_DESIGN && !this.HasSourceMaterial ||
|
||||
mode is VisualBriefingEditMode.CHANGE_DESIGN or VisualBriefingEditMode.UPDATE_CONTENT &&
|
||||
!this.SelectedVersionSupportsEdits ||
|
||||
@ -55,8 +55,8 @@ public partial class VisualBriefingAssistant
|
||||
var parentRevisionId = parentRevisionOverride ??
|
||||
(generationBriefing.Versions.Count == 0 ? null : this.selectedRevisionId);
|
||||
|
||||
var generationProvider = this.provider;
|
||||
var generationProfile = this.profile;
|
||||
var generationProvider = this.editor.Provider;
|
||||
var generationProfile = this.editor.Profile;
|
||||
|
||||
var sessionKey = CreateBuildSessionKey(briefingId);
|
||||
if (this.AssistantSessionService.TryGetSnapshot(sessionKey)?.IsActive == true)
|
||||
@ -280,7 +280,7 @@ public partial class VisualBriefingAssistant
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.provider == ProviderSettings.NONE)
|
||||
if (this.editor.Provider == ProviderSettings.NONE)
|
||||
return;
|
||||
|
||||
await this.GenerateAsync(
|
||||
|
||||
@ -1,15 +1,12 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json;
|
||||
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Dialogs;
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools.Media;
|
||||
using AIStudio.Tools.Rust;
|
||||
|
||||
using DialogOptions = AIStudio.Dialogs.DialogOptions;
|
||||
using ComponentKind = AIStudio.Tools.Components;
|
||||
using ProviderSettings = AIStudio.Settings.Provider;
|
||||
|
||||
namespace AIStudio.Assistants.VisualBriefing;
|
||||
|
||||
@ -98,7 +95,7 @@ public partial class VisualBriefingAssistant
|
||||
{
|
||||
{ dialog => dialog.Message, T("Enter a new name for this visual briefing.") },
|
||||
{ dialog => dialog.InputHeaderText, T("Briefing name") },
|
||||
{ dialog => dialog.UserInput, this.projectName },
|
||||
{ dialog => dialog.UserInput, this.editor.Name },
|
||||
{ dialog => dialog.ConfirmText, T("Rename") },
|
||||
{ dialog => dialog.ConfirmColor, Color.Info },
|
||||
{ dialog => dialog.AllowEmptyInput, false },
|
||||
@ -188,36 +185,15 @@ public partial class VisualBriefingAssistant
|
||||
/// </summary>
|
||||
private async Task SaveCurrentAsync(bool reload = false)
|
||||
{
|
||||
if (this.selectedBriefing is null || string.IsNullOrWhiteSpace(this.projectName))
|
||||
if (this.selectedBriefing is null || string.IsNullOrWhiteSpace(this.editor.Name))
|
||||
return;
|
||||
|
||||
var settings = new VisualBriefingLocalSettings
|
||||
{
|
||||
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,
|
||||
};
|
||||
|
||||
var sources = this.sourceMaterial.Select(attachment => (attachment.FilePath, VisualBriefingSourceKind.SOURCE_MATERIAL))
|
||||
.Concat(this.visualAssets.Select(attachment => (attachment.FilePath, VisualBriefingSourceKind.VISUAL_ASSET)));
|
||||
|
||||
await this.Store.SaveProjectAsync(
|
||||
this.selectedBriefing.BriefingId,
|
||||
this.projectName,
|
||||
this.author,
|
||||
settings,
|
||||
sources);
|
||||
this.editor.Name,
|
||||
this.editor.Author,
|
||||
this.editor.ToSettings(),
|
||||
this.editor.ToSources());
|
||||
|
||||
this.lastPersistedState = this.BuildPersistenceFingerprint();
|
||||
|
||||
@ -228,7 +204,6 @@ public partial class VisualBriefingAssistant
|
||||
/// <summary>
|
||||
/// Defines <c>ApplySelectedBriefingAsync</c> for the visual briefing feature.
|
||||
/// </summary>
|
||||
[SuppressMessage("Usage", "MWAIS0001:Direct access to `Providers` is not allowed")]
|
||||
private async Task ApplySelectedBriefingAsync(VisualBriefingManifest briefing)
|
||||
{
|
||||
await this.Store.RememberSelectionAsync(briefing.BriefingId);
|
||||
@ -246,41 +221,7 @@ public partial class VisualBriefingAssistant
|
||||
.FirstOrDefault(build => build.Status is VisualBriefingBuildStatus.AWAITING_REBUILD)
|
||||
?.BuildId;
|
||||
|
||||
this.projectName = briefing.Name;
|
||||
this.author = briefing.Author;
|
||||
this.instruction = briefing.Settings.Instruction;
|
||||
this.targetLanguage = briefing.Settings.TargetLanguage;
|
||||
this.customTargetLanguage = briefing.Settings.CustomTargetLanguage;
|
||||
this.audienceProfile = briefing.Settings.AudienceProfile;
|
||||
this.audienceAgeGroup = briefing.Settings.AudienceAgeGroup;
|
||||
this.audienceOrganizationalLevel = briefing.Settings.AudienceOrganizationalLevel;
|
||||
this.audienceExpertise = briefing.Settings.AudienceExpertise;
|
||||
this.showSourceReferences = briefing.Settings.ShowSourceReferences;
|
||||
this.optimizeImages = briefing.Settings.OptimizeImages;
|
||||
this.protectionLevel = briefing.Settings.ProtectionLevel;
|
||||
this.customProtectionLevel = briefing.Settings.CustomProtectionLevel;
|
||||
|
||||
this.provider = this.SettingsManager.ConfigurationData.Providers
|
||||
.FirstOrDefault(candidate =>
|
||||
candidate.Id == briefing.Settings.ProviderId &&
|
||||
candidate.Model.Id == briefing.Settings.ModelId) ?? ProviderSettings.NONE;
|
||||
|
||||
this.profile = this.SettingsManager.ConfigurationData.Profiles
|
||||
.FirstOrDefault(candidate => candidate.Id == briefing.Settings.ProfileId) ?? Profile.NO_PROFILE;
|
||||
|
||||
this.sourceMaterial =
|
||||
[
|
||||
.. briefing.Sources
|
||||
.Where(source => source.Kind is VisualBriefingSourceKind.SOURCE_MATERIAL)
|
||||
.Select(source => FileAttachment.FromPath(source.Path))
|
||||
];
|
||||
|
||||
this.visualAssets =
|
||||
[
|
||||
.. briefing.Sources
|
||||
.Where(source => source.Kind is VisualBriefingSourceKind.VISUAL_ASSET)
|
||||
.Select(source => FileAttachment.FromPath(source.Path))
|
||||
];
|
||||
this.editor = VisualBriefingEditorState.FromManifest(briefing, this.SettingsManager);
|
||||
|
||||
var revisionId = briefing.Versions.Any(version => version.RevisionId == this.selectedRevisionId)
|
||||
? this.selectedRevisionId
|
||||
@ -323,8 +264,7 @@ public partial class VisualBriefingAssistant
|
||||
{
|
||||
this.selectedProject = null;
|
||||
this.selectedBriefing = null;
|
||||
this.sourceMaterial = [];
|
||||
this.visualAssets = [];
|
||||
this.editor = new();
|
||||
this.selectedRevisionId = Guid.Empty;
|
||||
this.previewUrl = string.Empty;
|
||||
this.latestBuild = null;
|
||||
@ -355,7 +295,7 @@ public partial class VisualBriefingAssistant
|
||||
private string ProjectDisplayName(VisualBriefingProjectEntry project)
|
||||
{
|
||||
if (project.BriefingId == this.selectedBriefing?.BriefingId)
|
||||
return this.projectName;
|
||||
return this.editor.Name;
|
||||
|
||||
return string.IsNullOrWhiteSpace(project.Name) ? T("Unavailable visual briefing") : project.Name;
|
||||
}
|
||||
@ -393,25 +333,22 @@ public partial class VisualBriefingAssistant
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Defines <c>BuildPersistenceFingerprint</c> for the visual briefing feature.
|
||||
/// Builds the fingerprint that decides whether the editor holds unsaved changes.
|
||||
/// </summary>
|
||||
private string BuildPersistenceFingerprint() => string.Join('\u001f',
|
||||
this.projectName,
|
||||
this.author,
|
||||
this.instruction,
|
||||
this.provider.Id,
|
||||
this.provider.Model.Id,
|
||||
this.profile.Id,
|
||||
this.targetLanguage,
|
||||
this.customTargetLanguage,
|
||||
this.audienceProfile,
|
||||
this.audienceAgeGroup,
|
||||
this.audienceOrganizationalLevel,
|
||||
this.audienceExpertise,
|
||||
this.showSourceReferences,
|
||||
this.optimizeImages,
|
||||
this.protectionLevel,
|
||||
this.customProtectionLevel,
|
||||
string.Join('\u001e', this.sourceMaterial.Select(attachment => attachment.FilePath).Order(StringComparer.Ordinal)),
|
||||
string.Join('\u001e', this.visualAssets.Select(attachment => attachment.FilePath).Order(StringComparer.Ordinal)));
|
||||
/// <remarks>
|
||||
/// 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
|
||||
/// 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
|
||||
{
|
||||
this.editor.Name,
|
||||
this.editor.Author,
|
||||
Settings = this.editor.ToSettings(),
|
||||
Sources = this.editor.ToSources().Select(source => new { source.Path, source.Kind }).ToArray(),
|
||||
}, VisualBriefingJson.Compact);
|
||||
}
|
||||
@ -20,8 +20,8 @@ public partial class VisualBriefingAssistant
|
||||
/// </summary>
|
||||
private async Task SourceMaterialChangedAsync(HashSet<FileAttachment> _)
|
||||
{
|
||||
var visualPaths = this.visualAssets.Select(attachment => attachment.FilePath).ToHashSet(PathComparer());
|
||||
this.sourceMaterial.RemoveWhere(attachment => visualPaths.Contains(attachment.FilePath));
|
||||
var visualPaths = this.editor.VisualAssets.Select(attachment => attachment.FilePath).ToHashSet(PathComparer());
|
||||
this.editor.SourceMaterial.RemoveWhere(attachment => visualPaths.Contains(attachment.FilePath));
|
||||
await this.SaveCurrentAsync(reload: true);
|
||||
}
|
||||
|
||||
@ -30,8 +30,8 @@ public partial class VisualBriefingAssistant
|
||||
/// </summary>
|
||||
private async Task VisualAssetsChangedAsync(HashSet<FileAttachment> _)
|
||||
{
|
||||
var visualPaths = this.visualAssets.Select(attachment => attachment.FilePath).ToHashSet(PathComparer());
|
||||
this.sourceMaterial.RemoveWhere(attachment => visualPaths.Contains(attachment.FilePath));
|
||||
var visualPaths = this.editor.VisualAssets.Select(attachment => attachment.FilePath).ToHashSet(PathComparer());
|
||||
this.editor.SourceMaterial.RemoveWhere(attachment => visualPaths.Contains(attachment.FilePath));
|
||||
await this.SaveCurrentAsync(reload: true);
|
||||
}
|
||||
|
||||
|
||||
@ -16,10 +16,10 @@ public partial class VisualBriefingAssistant
|
||||
{
|
||||
List<string> issues = [.. this.formIssues];
|
||||
|
||||
AddIssue(issues, this.ValidateProjectName(this.projectName));
|
||||
AddIssue(issues, this.ValidateProvider(this.provider));
|
||||
AddIssue(issues, this.ValidateCustomTargetLanguage(this.customTargetLanguage));
|
||||
AddIssue(issues, this.ValidateCustomProtectionLevel(this.customProtectionLevel));
|
||||
AddIssue(issues, this.ValidateProjectName(this.editor.Name));
|
||||
AddIssue(issues, this.ValidateProvider(this.editor.Provider));
|
||||
AddIssue(issues, this.ValidateCustomTargetLanguage(this.editor.CustomTargetLanguage));
|
||||
AddIssue(issues, this.ValidateCustomProtectionLevel(this.editor.CustomProtectionLevel));
|
||||
|
||||
if (this.selectedBriefing is not null)
|
||||
{
|
||||
@ -60,13 +60,13 @@ public partial class VisualBriefingAssistant
|
||||
|
||||
/// <summary>Validates the free-form target language when Other is selected.</summary>
|
||||
private string? ValidateCustomTargetLanguage(string language) =>
|
||||
this.targetLanguage is CommonLanguages.OTHER && string.IsNullOrWhiteSpace(language)
|
||||
this.editor.TargetLanguage is CommonLanguages.OTHER && string.IsNullOrWhiteSpace(language)
|
||||
? T("Please provide a custom target language.")
|
||||
: null;
|
||||
|
||||
/// <summary>Validates the free-form protection level when Other is selected.</summary>
|
||||
private string? ValidateCustomProtectionLevel(string level) =>
|
||||
this.protectionLevel is VisualBriefingProtectionLevel.OTHER && string.IsNullOrWhiteSpace(level)
|
||||
this.editor.ProtectionLevel is VisualBriefingProtectionLevel.OTHER && string.IsNullOrWhiteSpace(level)
|
||||
? T("Please provide a custom protection level.")
|
||||
: null;
|
||||
|
||||
|
||||
@ -111,7 +111,7 @@ public partial class VisualBriefingAssistant
|
||||
var response = await this.RustService.SaveFile(
|
||||
T("Export visual briefing"),
|
||||
[FileTypes.VISUAL_BRIEFING_HTML],
|
||||
$"{SafeFileName(this.projectName)}.html");
|
||||
$"{SafeFileName(this.editor.Name)}.html");
|
||||
|
||||
if (response.UserCancelled)
|
||||
return;
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
using AIStudio.Assistants.SlideBuilder;
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Components;
|
||||
using AIStudio.Dialogs;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools.AssistantSessions;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
@ -10,7 +7,6 @@ using Microsoft.AspNetCore.Components;
|
||||
|
||||
using DialogOptions = AIStudio.Dialogs.DialogOptions;
|
||||
using ComponentKind = AIStudio.Tools.Components;
|
||||
using ProviderSettings = AIStudio.Settings.Provider;
|
||||
|
||||
namespace AIStudio.Assistants.VisualBriefing;
|
||||
|
||||
@ -100,56 +96,8 @@ public partial class VisualBriefingAssistant : MSGComponentBase
|
||||
/// <summary>Stores the project currently displayed by the editor.</summary>
|
||||
private VisualBriefingManifest? selectedBriefing;
|
||||
|
||||
/// <summary>Stores source-material attachments for the selected project.</summary>
|
||||
private HashSet<FileAttachment> sourceMaterial = [];
|
||||
|
||||
/// <summary>Stores visible visual-asset attachments for the selected project.</summary>
|
||||
private HashSet<FileAttachment> visualAssets = [];
|
||||
|
||||
/// <summary>Stores the editable project name.</summary>
|
||||
private string projectName = string.Empty;
|
||||
|
||||
/// <summary>Stores the optional author.</summary>
|
||||
private string author = string.Empty;
|
||||
|
||||
/// <summary>Stores the current scope or change instruction.</summary>
|
||||
private string instruction = string.Empty;
|
||||
|
||||
/// <summary>Stores the selected provider and model.</summary>
|
||||
private ProviderSettings provider = ProviderSettings.NONE;
|
||||
|
||||
/// <summary>Stores the selected profile.</summary>
|
||||
private Profile profile = Profile.NO_PROFILE;
|
||||
|
||||
/// <summary>Stores the selected target language.</summary>
|
||||
private CommonLanguages targetLanguage = CommonLanguages.EN_US;
|
||||
|
||||
/// <summary>Stores a free-form target language.</summary>
|
||||
private string customTargetLanguage = string.Empty;
|
||||
|
||||
/// <summary>Stores the audience profile.</summary>
|
||||
private AudienceProfile audienceProfile;
|
||||
|
||||
/// <summary>Stores the audience age group.</summary>
|
||||
private AudienceAgeGroup audienceAgeGroup;
|
||||
|
||||
/// <summary>Stores the audience organizational level.</summary>
|
||||
private AudienceOrganizationalLevel audienceOrganizationalLevel;
|
||||
|
||||
/// <summary>Stores the audience expertise.</summary>
|
||||
private AudienceExpertise audienceExpertise;
|
||||
|
||||
/// <summary>Stores whether visible source references are requested.</summary>
|
||||
private bool showSourceReferences = true;
|
||||
|
||||
/// <summary>Stores whether large visual assets are optimized.</summary>
|
||||
private bool optimizeImages = true;
|
||||
|
||||
/// <summary>Stores the selected protection level.</summary>
|
||||
private VisualBriefingProtectionLevel protectionLevel = VisualBriefingProtectionLevel.INTERNAL;
|
||||
|
||||
/// <summary>Stores the free-form protection level.</summary>
|
||||
private string customProtectionLevel = string.Empty;
|
||||
/// <summary>Stores every editable value of the selected briefing.</summary>
|
||||
private VisualBriefingEditorState editor = new();
|
||||
|
||||
/// <summary>Stores the selected immutable revision.</summary>
|
||||
private Guid selectedRevisionId;
|
||||
@ -221,7 +169,7 @@ public partial class VisualBriefingAssistant : MSGComponentBase
|
||||
if (this.selectedBriefing is null)
|
||||
await this.CreateBriefingAsync();
|
||||
|
||||
this.instruction = deferredInstruction;
|
||||
this.editor.Instruction = deferredInstruction;
|
||||
await this.SaveCurrentAsync();
|
||||
}
|
||||
|
||||
@ -286,7 +234,7 @@ public partial class VisualBriefingAssistant : MSGComponentBase
|
||||
if (this.selectedBriefing is null)
|
||||
await this.CreateBriefingAsync();
|
||||
|
||||
this.instruction = text;
|
||||
this.editor.Instruction = text;
|
||||
await this.SaveCurrentAsync();
|
||||
this.StateHasChanged();
|
||||
return;
|
||||
|
||||
@ -0,0 +1,161 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
using AIStudio.Assistants.SlideBuilder;
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Settings;
|
||||
|
||||
using ProviderSettings = AIStudio.Settings.Provider;
|
||||
|
||||
namespace AIStudio.Assistants.VisualBriefing;
|
||||
|
||||
/// <summary>
|
||||
/// Holds the editable state of one visual briefing while the user works on it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is the single source of truth for the briefing editor. It exists because the editor cannot
|
||||
/// bind to <see cref="VisualBriefingLocalSettings"/> directly: that type stores the provider, model,
|
||||
/// and profile as identifiers, while the UI binds whole <see cref="ProviderSettings"/> and
|
||||
/// <see cref="Profile"/> objects. Keeping one draft object means saving, restoring, and change
|
||||
/// detection all read the same fields instead of three hand-maintained lists.
|
||||
/// </remarks>
|
||||
public sealed class VisualBriefingEditorState
|
||||
{
|
||||
/// <summary>Gets or sets the briefing name.</summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Gets or sets the optional author.</summary>
|
||||
public string Author { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Gets or sets the selected provider and model.</summary>
|
||||
public ProviderSettings Provider { get; set; } = ProviderSettings.NONE;
|
||||
|
||||
/// <summary>Gets or sets the selected profile.</summary>
|
||||
public Profile Profile { get; set; } = Profile.NO_PROFILE;
|
||||
|
||||
/// <summary>Gets or sets the current scope or change instruction.</summary>
|
||||
public string Instruction { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Gets or sets the selected target language.</summary>
|
||||
public CommonLanguages TargetLanguage { get; set; } = CommonLanguages.EN_US;
|
||||
|
||||
/// <summary>Gets or sets a free-form target language.</summary>
|
||||
public string CustomTargetLanguage { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Gets or sets the audience profile.</summary>
|
||||
public AudienceProfile AudienceProfile { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the audience age group.</summary>
|
||||
public AudienceAgeGroup AudienceAgeGroup { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the audience organizational level.</summary>
|
||||
public AudienceOrganizationalLevel AudienceOrganizationalLevel { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the audience expertise.</summary>
|
||||
public AudienceExpertise AudienceExpertise { get; set; }
|
||||
|
||||
/// <summary>Gets or sets whether visible source references are requested.</summary>
|
||||
public bool ShowSourceReferences { get; set; } = true;
|
||||
|
||||
/// <summary>Gets or sets whether large visual assets are optimized.</summary>
|
||||
public bool OptimizeImages { get; set; } = true;
|
||||
|
||||
/// <summary>Gets or sets the selected protection level.</summary>
|
||||
public VisualBriefingProtectionLevel ProtectionLevel { get; set; } = VisualBriefingProtectionLevel.INTERNAL;
|
||||
|
||||
/// <summary>Gets or sets the free-form protection level.</summary>
|
||||
public string CustomProtectionLevel { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Gets or sets the source-material attachments.</summary>
|
||||
public HashSet<FileAttachment> SourceMaterial { get; set; } = [];
|
||||
|
||||
/// <summary>Gets or sets the visual-asset attachments.</summary>
|
||||
public HashSet<FileAttachment> VisualAssets { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Creates the editor state for a stored briefing.
|
||||
/// </summary>
|
||||
/// <param name="briefing">The manifest to read.</param>
|
||||
/// <param name="settingsManager">The settings used to resolve the stored provider and profile.</param>
|
||||
/// <returns>The editor state for the briefing.</returns>
|
||||
[SuppressMessage("Usage", "MWAIS0001:Direct access to `Providers` is not allowed", Justification = "A stored briefing references one specific provider and model by id, so it must be looked up directly instead of using the preselection APIs.")]
|
||||
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 = settingsManager.ConfigurationData.Providers.FirstOrDefault(candidate => candidate.Id == briefing.Settings.ProviderId && candidate.Model.Id == briefing.Settings.ModelId) ?? ProviderSettings.NONE,
|
||||
Profile = settingsManager.ConfigurationData.Profiles.FirstOrDefault(candidate => candidate.Id == briefing.Settings.ProfileId) ?? Profile.NO_PROFILE,
|
||||
|
||||
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))
|
||||
],
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Creates the persisted settings for this editor state.
|
||||
/// </summary>
|
||||
/// <returns>The settings to store.</returns>
|
||||
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,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Creates the persisted source list for this editor state.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
/// <returns>The sources to store, in a stable order.</returns>
|
||||
public IEnumerable<(string Path, VisualBriefingSourceKind Kind)> ToSources() =>
|
||||
OrderedSources(this.SourceMaterial, VisualBriefingSourceKind.SOURCE_MATERIAL)
|
||||
.Concat(OrderedSources(this.VisualAssets, VisualBriefingSourceKind.VISUAL_ASSET));
|
||||
|
||||
/// <summary>
|
||||
/// Orders one attachment set into stable source entries of a single kind.
|
||||
/// </summary>
|
||||
/// <param name="attachments">The attachments to convert.</param>
|
||||
/// <param name="kind">The kind to assign.</param>
|
||||
/// <returns>The ordered source entries.</returns>
|
||||
private static IEnumerable<(string Path, VisualBriefingSourceKind Kind)> OrderedSources(IEnumerable<FileAttachment> attachments, VisualBriefingSourceKind kind) => attachments
|
||||
.Select(attachment => attachment.FilePath)
|
||||
.Order(StringComparer.Ordinal)
|
||||
.Select(path => (path, kind));
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user