small ui fixes

This commit is contained in:
nilsk 2026-03-31 15:46:04 +02:00
parent 3f2d76337a
commit 92267b129f
5 changed files with 74 additions and 33 deletions

View File

@ -9,11 +9,11 @@ public static class AssistantAuditLevelExtensions
public static string GetName(this AssistantAuditLevel level) => level switch public static string GetName(this AssistantAuditLevel level) => level switch
{ {
AssistantAuditLevel.DANGEROUS => TB("Dangerous"), AssistantAuditLevel.DANGEROUS => TB("Dangerous"),
AssistantAuditLevel.CAUTION => TB("Needs Review"), AssistantAuditLevel.CAUTION => TB("Concerning"),
AssistantAuditLevel.SAFE => TB("Safe"), AssistantAuditLevel.SAFE => TB("Safe"),
_ => TB("Unknown"), _ => TB("Unknown"),
}; };
public static Severity GetSeverity(this AssistantAuditLevel level) => level switch public static Severity GetSeverity(this AssistantAuditLevel level) => level switch
{ {
AssistantAuditLevel.DANGEROUS => Severity.Error, AssistantAuditLevel.DANGEROUS => Severity.Error,
@ -22,5 +22,26 @@ public static class AssistantAuditLevelExtensions
_ => Severity.Info, _ => Severity.Info,
}; };
public static Color GetColor(this AssistantAuditLevel level) => level switch
{
AssistantAuditLevel.DANGEROUS => Color.Error,
AssistantAuditLevel.CAUTION => Color.Warning,
AssistantAuditLevel.SAFE => Color.Success,
_ => Color.Default,
};
public static string GetIcon(this AssistantAuditLevel level) => level switch
{
AssistantAuditLevel.DANGEROUS => Icons.Material.Filled.Dangerous,
AssistantAuditLevel.CAUTION => Icons.Material.Filled.Warning,
AssistantAuditLevel.SAFE => Icons.Material.Filled.Verified,
_ => Icons.Material.Filled.HelpOutline,
};
/// <summary>
/// Parses an audit level string and falls back to <see cref="AssistantAuditLevel.UNKNOWN"/> when parsing fails.
/// </summary>
/// <param name="value">The audit level text to parse.</param>
/// <returns>The parsed audit level, or <see cref="AssistantAuditLevel.UNKNOWN"/> for null, empty, or invalid values.</returns>
public static AssistantAuditLevel Parse(string? value) => Enum.TryParse<AssistantAuditLevel>(value, true, out var level) ? level : AssistantAuditLevel.UNKNOWN; public static AssistantAuditLevel Parse(string? value) => Enum.TryParse<AssistantAuditLevel>(value, true, out var level) ? level : AssistantAuditLevel.UNKNOWN;
} }

View File

@ -5,7 +5,21 @@
@using AIStudio.Tools.PluginSystem.Assistants.DataModel.Layout @using AIStudio.Tools.PluginSystem.Assistants.DataModel.Layout
@inherits AssistantBaseCore<AIStudio.Dialogs.Settings.SettingsDialogDynamic> @inherits AssistantBaseCore<AIStudio.Dialogs.Settings.SettingsDialogDynamic>
@if (this.RootComponent is null) @if (!string.IsNullOrWhiteSpace(this.securityMessage))
{
<MudPaper Class="pa-4 ma-4" Elevation="0">
<MudAlert Severity="Severity.Error" Variant="Variant.Filled" Square="false" Elevation="6" Class="pa-4">
@this.securityMessage
</MudAlert>
@if (this.assistantPlugin is not null)
{
<div class="mt-4">
<AssistantPluginSecurityCard Plugin="@this.assistantPlugin"/>
</div>
}
</MudPaper>
}
else if (this.RootComponent is null)
{ {
<MudAlert Severity="Severity.Warning"> <MudAlert Severity="Severity.Warning">
@this.T("No assistant plugin are currently installed.") @this.T("No assistant plugin are currently installed.")

View File

@ -22,6 +22,7 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
protected override bool ShowProfileSelection => this.showFooterProfileSelection; protected override bool ShowProfileSelection => this.showFooterProfileSelection;
protected override string SubmitText => this.submitText; protected override string SubmitText => this.submitText;
protected override Func<Task> SubmitAction => this.Submit; protected override Func<Task> SubmitAction => this.Submit;
protected override bool SubmitDisabled => this.isSecurityBlocked;
// Dynamic assistants do not have dedicated settings yet. // Dynamic assistants do not have dedicated settings yet.
// Reuse chat-level provider filtering/preselection instead of NONE. // Reuse chat-level provider filtering/preselection instead of NONE.
protected override Tools.Components Component => Tools.Components.CHAT; protected override Tools.Components Component => Tools.Components.CHAT;
@ -40,6 +41,8 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
private readonly HashSet<string> executingSwitchActions = []; private readonly HashSet<string> executingSwitchActions = [];
private string pluginPath = string.Empty; private string pluginPath = string.Empty;
private PluginAssistantAudit? audit; private PluginAssistantAudit? audit;
private string securityMessage = string.Empty;
private bool isSecurityBlocked;
private const string ASSISTANT_QUERY_KEY = "assistantId"; private const string ASSISTANT_QUERY_KEY = "assistantId";
#region Implementation of AssistantBase #region Implementation of AssistantBase
@ -66,6 +69,16 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
var pluginHash = pluginAssistant.ComputeAuditHash(); var pluginHash = pluginAssistant.ComputeAuditHash();
this.audit = this.SettingsManager.ConfigurationData.AssistantPluginAudits.FirstOrDefault(x => x.PluginId == pluginAssistant.Id && x.PluginHash == pluginHash); this.audit = this.SettingsManager.ConfigurationData.AssistantPluginAudits.FirstOrDefault(x => x.PluginId == pluginAssistant.Id && x.PluginHash == pluginHash);
var securityState = PluginAssistantSecurityResolver.Resolve(this.SettingsManager, pluginAssistant);
if (!securityState.CanStartAssistant)
{
this.assistantPlugin = pluginAssistant;
this.securityMessage = securityState.Description;
this.isSecurityBlocked = true;
base.OnInitialized();
return;
}
var rootComponent = this.RootComponent; var rootComponent = this.RootComponent;
if (rootComponent is not null) if (rootComponent is not null)
{ {
@ -387,6 +400,13 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
private async Task Submit() private async Task Submit()
{ {
if (this.assistantPlugin is not null)
{
var securityState = PluginAssistantSecurityResolver.Resolve(this.SettingsManager, this.assistantPlugin);
if (!securityState.CanStartAssistant)
return;
}
this.CreateChatThread(); this.CreateChatThread();
var time = this.AddUserRequest(await this.CollectUserPromptAsync()); var time = this.AddUserRequest(await this.CollectUserPromptAsync());
await this.AddAIResponseAsync(time); await this.AddAIResponseAsync(time);

View File

@ -1,5 +1,4 @@
@using AIStudio.Agents.AssistantAudit @using AIStudio.Agents.AssistantAudit
@using AIStudio.Components
@inherits MSGComponentBase @inherits MSGComponentBase
<MudDialog DefaultFocus="DefaultFocus.FirstChild"> <MudDialog DefaultFocus="DefaultFocus.FirstChild">
@ -299,7 +298,7 @@
<MudButton OnClick="@this.CloseWithoutActivation" Variant="Variant.Filled"> <MudButton OnClick="@this.CloseWithoutActivation" Variant="Variant.Filled">
@(this.audit is null ? T("Cancel") : T("Close")) @(this.audit is null ? T("Cancel") : T("Close"))
</MudButton> </MudButton>
<MudButton OnClick="@this.RunAudit" Variant="Variant.Filled" Color="Color.Primary" Disabled="@(!this.CanRunAudit)"> <MudButton OnClick="@this.RunAudit" Variant="Variant.Filled" Color="Color.Primary" Disabled="@(!this.CanRunAudit || this.justAudited)">
@T("Start Security Check") @T("Start Security Check")
</MudButton> </MudButton>
@if (this.CanEnablePlugin) @if (this.CanEnablePlugin)

View File

@ -1,3 +1,7 @@
using System.Collections;
using System.Collections.Immutable;
using System.Globalization;
using System.Reflection;
using AIStudio.Agents.AssistantAudit; using AIStudio.Agents.AssistantAudit;
using AIStudio.Components; using AIStudio.Components;
using AIStudio.Provider; using AIStudio.Provider;
@ -6,17 +10,12 @@ using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.PluginSystem.Assistants; using AIStudio.Tools.PluginSystem.Assistants;
using AIStudio.Tools.PluginSystem.Assistants.DataModel; using AIStudio.Tools.PluginSystem.Assistants.DataModel;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using System.Collections;
using System.Collections.Immutable;
using System.Globalization;
using System.Reflection;
namespace AIStudio.Dialogs; namespace AIStudio.Dialogs;
public partial class AssistantPluginAuditDialog : MSGComponentBase public partial class AssistantPluginAuditDialog : MSGComponentBase
{ {
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(AssistantPluginAuditDialog).Namespace, private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(AssistantPluginAuditDialog).Namespace, nameof(AssistantPluginAuditDialog));
nameof(AssistantPluginAuditDialog));
[CascadingParameter] [CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = null!; private IMudDialogInstance MudDialog { get; set; } = null!;
@ -37,7 +36,7 @@ public partial class AssistantPluginAuditDialog : MSGComponentBase
private ImmutableDictionary<string, string> luaFiles = ImmutableDictionary.Create<string, string>(); private ImmutableDictionary<string, string> luaFiles = ImmutableDictionary.Create<string, string>();
private IReadOnlyCollection<TreeItemData<ITreeItem>> componentTreeItems = []; private IReadOnlyCollection<TreeItemData<ITreeItem>> componentTreeItems = [];
private IReadOnlyCollection<TreeItemData<ITreeItem>> fileSystemTreeItems = []; private IReadOnlyCollection<TreeItemData<ITreeItem>> fileSystemTreeItems = [];
private CultureInfo fileInfoCulture = CultureInfo.InvariantCulture; private CultureInfo currentCultureInfo = CultureInfo.InvariantCulture;
private bool isAuditing; private bool isAuditing;
private AIStudio.Settings.Provider CurrentProvider => this.SettingsManager.GetPreselectedProvider(Tools.Components.AGENT_ASSISTANT_PLUGIN_AUDIT, null, true); private AIStudio.Settings.Provider CurrentProvider => this.SettingsManager.GetPreselectedProvider(Tools.Components.AGENT_ASSISTANT_PLUGIN_AUDIT, null, true);
@ -63,13 +62,14 @@ public partial class AssistantPluginAuditDialog : MSGComponentBase
private bool CanEnablePlugin => this.audit is not null && !this.isAuditing && !this.IsActivationBlockedBySettings; private bool CanEnablePlugin => this.audit is not null && !this.isAuditing && !this.IsActivationBlockedBySettings;
private Color EnableButtonColor => this.RequiresActivationConfirmation ? Color.Warning : Color.Success; private Color EnableButtonColor => this.RequiresActivationConfirmation ? Color.Warning : Color.Success;
private bool justAudited;
private const ushort BYTES_PER_KILOBYTE = 1024; private const ushort BYTES_PER_KILOBYTE = 1024;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
var activeLanguagePlugin = await this.SettingsManager.GetActiveLanguagePlugin(); var activeLanguagePlugin = await this.SettingsManager.GetActiveLanguagePlugin();
this.fileInfoCulture = this.CreateFileInfoCulture(activeLanguagePlugin.IETFTag); this.currentCultureInfo = CommonTools.DeriveActiveCultureOrInvariant(activeLanguagePlugin.IETFTag);
this.plugin = PluginFactory.RunningPlugins.OfType<PluginAssistants>() this.plugin = PluginFactory.RunningPlugins.OfType<PluginAssistants>()
.FirstOrDefault(x => x.Id == this.PluginId); .FirstOrDefault(x => x.Id == this.PluginId);
@ -116,6 +116,7 @@ public partial class AssistantPluginAuditDialog : MSGComponentBase
finally finally
{ {
this.isAuditing = false; this.isAuditing = false;
this.justAudited = true;
await this.InvokeAsync(this.StateHasChanged); await this.InvokeAsync(this.StateHasChanged);
} }
} }
@ -453,7 +454,7 @@ public partial class AssistantPluginAuditDialog : MSGComponentBase
string stringValue when string.IsNullOrWhiteSpace(stringValue) => TB("empty"), string stringValue when string.IsNullOrWhiteSpace(stringValue) => TB("empty"),
string stringValue => stringValue, string stringValue => stringValue,
bool boolValue => boolValue ? "true" : "false", bool boolValue => boolValue ? "true" : "false",
_ => Convert.ToString(value, System.Globalization.CultureInfo.InvariantCulture) ?? string.Empty, _ => Convert.ToString(value, CultureInfo.InvariantCulture) ?? string.Empty,
}; };
private string GetStructuredValueCaption(object? value) => value switch private string GetStructuredValueCaption(object? value) => value switch
@ -477,37 +478,23 @@ public partial class AssistantPluginAuditDialog : MSGComponentBase
_ => Icons.Material.Filled.DataArray, _ => Icons.Material.Filled.DataArray,
}; };
private string FormatFileTimestamp(DateTime timestamp) => timestamp.ToString("g", this.fileInfoCulture); private string FormatFileTimestamp(DateTime timestamp) => CommonTools.FormatTimestampToGeneral(timestamp, this.currentCultureInfo);
private string FormatFileSize(long bytes) private string FormatFileSize(long bytes)
{ {
if (bytes < BYTES_PER_KILOBYTE) if (bytes < BYTES_PER_KILOBYTE)
return string.Format(this.fileInfoCulture, TB("{0} B"), bytes); return string.Format(this.currentCultureInfo, TB("{0} B"), bytes);
var kilobyte = bytes / (double)BYTES_PER_KILOBYTE; var kilobyte = bytes / (double)BYTES_PER_KILOBYTE;
if (kilobyte < BYTES_PER_KILOBYTE) if (kilobyte < BYTES_PER_KILOBYTE)
return string.Format(this.fileInfoCulture, TB("{0:0.##} KB"), kilobyte); return string.Format(this.currentCultureInfo, TB("{0:0.##} KB"), kilobyte);
var megabyte = kilobyte / BYTES_PER_KILOBYTE; var megabyte = kilobyte / BYTES_PER_KILOBYTE;
if (megabyte < BYTES_PER_KILOBYTE) if (megabyte < BYTES_PER_KILOBYTE)
return string.Format(this.fileInfoCulture, TB("{0:0.##} MB"), megabyte); return string.Format(this.currentCultureInfo, TB("{0:0.##} MB"), megabyte);
var gigabyte = megabyte / BYTES_PER_KILOBYTE; var gigabyte = megabyte / BYTES_PER_KILOBYTE;
return string.Format(this.fileInfoCulture, TB("{0:0.##} GB"), gigabyte); return string.Format(this.currentCultureInfo, TB("{0:0.##} GB"), gigabyte);
} }
private CultureInfo CreateFileInfoCulture(string ietfTag)
{
if (string.IsNullOrWhiteSpace(ietfTag))
return CultureInfo.InvariantCulture;
try
{
return CultureInfo.GetCultureInfo(ietfTag);
}
catch (CultureNotFoundException)
{
return CultureInfo.InvariantCulture;
}
}
} }