diff --git a/app/MindWork AI Studio/Agents/AssistantAudit/AssistantAuditLevelExtensions.cs b/app/MindWork AI Studio/Agents/AssistantAudit/AssistantAuditLevelExtensions.cs
index 54f57329..4e7b05dd 100644
--- a/app/MindWork AI Studio/Agents/AssistantAudit/AssistantAuditLevelExtensions.cs
+++ b/app/MindWork AI Studio/Agents/AssistantAudit/AssistantAuditLevelExtensions.cs
@@ -9,11 +9,11 @@ public static class AssistantAuditLevelExtensions
public static string GetName(this AssistantAuditLevel level) => level switch
{
AssistantAuditLevel.DANGEROUS => TB("Dangerous"),
- AssistantAuditLevel.CAUTION => TB("Needs Review"),
+ AssistantAuditLevel.CAUTION => TB("Concerning"),
AssistantAuditLevel.SAFE => TB("Safe"),
_ => TB("Unknown"),
};
-
+
public static Severity GetSeverity(this AssistantAuditLevel level) => level switch
{
AssistantAuditLevel.DANGEROUS => Severity.Error,
@@ -22,5 +22,26 @@ public static class AssistantAuditLevelExtensions
_ => 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,
+ };
+
+ ///
+ /// Parses an audit level string and falls back to when parsing fails.
+ ///
+ /// The audit level text to parse.
+ /// The parsed audit level, or for null, empty, or invalid values.
public static AssistantAuditLevel Parse(string? value) => Enum.TryParse(value, true, out var level) ? level : AssistantAuditLevel.UNKNOWN;
}
diff --git a/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor b/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor
index 826052cc..e1a1729c 100644
--- a/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor
+++ b/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor
@@ -5,7 +5,21 @@
@using AIStudio.Tools.PluginSystem.Assistants.DataModel.Layout
@inherits AssistantBaseCore
-@if (this.RootComponent is null)
+@if (!string.IsNullOrWhiteSpace(this.securityMessage))
+{
+
+
+ @this.securityMessage
+
+ @if (this.assistantPlugin is not null)
+ {
+
+ }
+
+}
+else if (this.RootComponent is null)
{
@this.T("No assistant plugin are currently installed.")
diff --git a/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor.cs b/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor.cs
index 4f7c512c..0e44e832 100644
--- a/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor.cs
+++ b/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor.cs
@@ -22,6 +22,7 @@ public partial class AssistantDynamic : AssistantBaseCore
protected override bool ShowProfileSelection => this.showFooterProfileSelection;
protected override string SubmitText => this.submitText;
protected override Func SubmitAction => this.Submit;
+ protected override bool SubmitDisabled => this.isSecurityBlocked;
// Dynamic assistants do not have dedicated settings yet.
// Reuse chat-level provider filtering/preselection instead of NONE.
protected override Tools.Components Component => Tools.Components.CHAT;
@@ -40,6 +41,8 @@ public partial class AssistantDynamic : AssistantBaseCore
private readonly HashSet executingSwitchActions = [];
private string pluginPath = string.Empty;
private PluginAssistantAudit? audit;
+ private string securityMessage = string.Empty;
+ private bool isSecurityBlocked;
private const string ASSISTANT_QUERY_KEY = "assistantId";
#region Implementation of AssistantBase
@@ -66,6 +69,16 @@ public partial class AssistantDynamic : AssistantBaseCore
var pluginHash = pluginAssistant.ComputeAuditHash();
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;
if (rootComponent is not null)
{
@@ -387,6 +400,13 @@ public partial class AssistantDynamic : AssistantBaseCore
private async Task Submit()
{
+ if (this.assistantPlugin is not null)
+ {
+ var securityState = PluginAssistantSecurityResolver.Resolve(this.SettingsManager, this.assistantPlugin);
+ if (!securityState.CanStartAssistant)
+ return;
+ }
+
this.CreateChatThread();
var time = this.AddUserRequest(await this.CollectUserPromptAsync());
await this.AddAIResponseAsync(time);
diff --git a/app/MindWork AI Studio/Dialogs/AssistantPluginAuditDialog.razor b/app/MindWork AI Studio/Dialogs/AssistantPluginAuditDialog.razor
index df49447d..637f3329 100644
--- a/app/MindWork AI Studio/Dialogs/AssistantPluginAuditDialog.razor
+++ b/app/MindWork AI Studio/Dialogs/AssistantPluginAuditDialog.razor
@@ -1,5 +1,4 @@
@using AIStudio.Agents.AssistantAudit
-@using AIStudio.Components
@inherits MSGComponentBase
@@ -299,7 +298,7 @@
@(this.audit is null ? T("Cancel") : T("Close"))
-
+
@T("Start Security Check")
@if (this.CanEnablePlugin)
diff --git a/app/MindWork AI Studio/Dialogs/AssistantPluginAuditDialog.razor.cs b/app/MindWork AI Studio/Dialogs/AssistantPluginAuditDialog.razor.cs
index 266e98d6..0c01ae12 100644
--- a/app/MindWork AI Studio/Dialogs/AssistantPluginAuditDialog.razor.cs
+++ b/app/MindWork AI Studio/Dialogs/AssistantPluginAuditDialog.razor.cs
@@ -1,3 +1,7 @@
+using System.Collections;
+using System.Collections.Immutable;
+using System.Globalization;
+using System.Reflection;
using AIStudio.Agents.AssistantAudit;
using AIStudio.Components;
using AIStudio.Provider;
@@ -6,17 +10,12 @@ using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.PluginSystem.Assistants;
using AIStudio.Tools.PluginSystem.Assistants.DataModel;
using Microsoft.AspNetCore.Components;
-using System.Collections;
-using System.Collections.Immutable;
-using System.Globalization;
-using System.Reflection;
namespace AIStudio.Dialogs;
public partial class AssistantPluginAuditDialog : MSGComponentBase
{
- private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(AssistantPluginAuditDialog).Namespace,
- nameof(AssistantPluginAuditDialog));
+ private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(AssistantPluginAuditDialog).Namespace, nameof(AssistantPluginAuditDialog));
[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = null!;
@@ -37,7 +36,7 @@ public partial class AssistantPluginAuditDialog : MSGComponentBase
private ImmutableDictionary luaFiles = ImmutableDictionary.Create();
private IReadOnlyCollection> componentTreeItems = [];
private IReadOnlyCollection> fileSystemTreeItems = [];
- private CultureInfo fileInfoCulture = CultureInfo.InvariantCulture;
+ private CultureInfo currentCultureInfo = CultureInfo.InvariantCulture;
private bool isAuditing;
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 Color EnableButtonColor => this.RequiresActivationConfirmation ? Color.Warning : Color.Success;
+ private bool justAudited;
private const ushort BYTES_PER_KILOBYTE = 1024;
protected override async Task OnInitializedAsync()
{
var activeLanguagePlugin = await this.SettingsManager.GetActiveLanguagePlugin();
- this.fileInfoCulture = this.CreateFileInfoCulture(activeLanguagePlugin.IETFTag);
+ this.currentCultureInfo = CommonTools.DeriveActiveCultureOrInvariant(activeLanguagePlugin.IETFTag);
this.plugin = PluginFactory.RunningPlugins.OfType()
.FirstOrDefault(x => x.Id == this.PluginId);
@@ -116,6 +116,7 @@ public partial class AssistantPluginAuditDialog : MSGComponentBase
finally
{
this.isAuditing = false;
+ this.justAudited = true;
await this.InvokeAsync(this.StateHasChanged);
}
}
@@ -453,7 +454,7 @@ public partial class AssistantPluginAuditDialog : MSGComponentBase
string stringValue when string.IsNullOrWhiteSpace(stringValue) => TB("empty"),
string stringValue => stringValue,
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
@@ -477,37 +478,23 @@ public partial class AssistantPluginAuditDialog : MSGComponentBase
_ => 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)
{
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;
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;
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;
- 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;
- }
- }
}