@@ -30,6 +31,29 @@
}
+ @if (this.AssistantPlugins.Count > 0)
+ {
+
+ @T("Installed Assistants")
+
+
+ @foreach (var assistantPlugin in this.AssistantPlugins)
+ {
+ var securityState = PluginAssistantSecurityResolver.Resolve(this.SettingsManager, assistantPlugin);
+
+
+
+
+
+ }
+
+ }
+
@if (this.SettingsManager.IsAnyCategoryAssistantVisible("Business",
(Components.EMAIL_ASSISTANT, PreviewFeatures.NONE),
(Components.DOCUMENT_ANALYSIS_ASSISTANT, PreviewFeatures.NONE),
diff --git a/app/MindWork AI Studio/Pages/Assistants.razor.cs b/app/MindWork AI Studio/Pages/Assistants.razor.cs
index e2c2de49..f7668a1d 100644
--- a/app/MindWork AI Studio/Pages/Assistants.razor.cs
+++ b/app/MindWork AI Studio/Pages/Assistants.razor.cs
@@ -1,5 +1,92 @@
using AIStudio.Components;
+using AIStudio.Agents.AssistantAudit;
+using AIStudio.Tools.PluginSystem;
+using AIStudio.Tools.PluginSystem.Assistants;
+using Microsoft.AspNetCore.Components;
namespace AIStudio.Pages;
-public partial class Assistants : MSGComponentBase;
\ No newline at end of file
+public partial class Assistants : MSGComponentBase
+{
+ private bool isAutoAuditing;
+
+ [Inject]
+ private AssistantPluginAuditService AssistantPluginAuditService { get; init; } = null!;
+
+ protected override async Task OnInitializedAsync()
+ {
+ this.ApplyFilters([], [ Event.CONFIGURATION_CHANGED, Event.PLUGINS_RELOADED ]);
+ await base.OnInitializedAsync();
+ }
+
+ protected override async Task OnAfterRenderAsync(bool firstRender)
+ {
+ if (firstRender)
+ await this.TryAutoAuditAssistantsAsync();
+ }
+
+ private IReadOnlyCollection
AssistantPlugins =>
+ PluginFactory.RunningPlugins.OfType()
+ .Where(plugin => this.SettingsManager.IsPluginEnabled(plugin))
+ .ToList();
+
+ private async Task TryAutoAuditAssistantsAsync()
+ {
+ if (this.isAutoAuditing || !this.SettingsManager.ConfigurationData.AssistantPluginAudit.AutomaticallyAuditAssistants)
+ return;
+
+ this.isAutoAuditing = true;
+
+ try
+ {
+ var wasConfigurationChanged = false;
+ var assistantPlugins = PluginFactory.RunningPlugins.OfType().ToList();
+ foreach (var assistantPlugin in assistantPlugins)
+ {
+ var securityState = PluginAssistantSecurityResolver.Resolve(this.SettingsManager, assistantPlugin);
+ if (!securityState.RequiresAudit)
+ continue;
+
+ var audit = await this.AssistantPluginAuditService.RunAuditAsync(assistantPlugin);
+ if (audit.Level is AssistantAuditLevel.UNKNOWN)
+ {
+ await MessageBus.INSTANCE.SendError(new (Icons.Material.Filled.SettingsSuggest, string.Format(this.T("The automatic security audit for the assistant plugin '{0}' failed. Please run it manually from the plugins page."), assistantPlugin.Name)));
+ continue;
+ }
+
+ this.UpsertAuditCard(audit);
+ wasConfigurationChanged = true;
+ }
+
+ if (!wasConfigurationChanged)
+ return;
+
+ await this.SettingsManager.StoreSettings();
+ await this.MessageBus.SendMessage(this, Event.CONFIGURATION_CHANGED);
+ }
+ finally
+ {
+ this.isAutoAuditing = false;
+ await this.InvokeAsync(this.StateHasChanged);
+ }
+ }
+
+ private void UpsertAuditCard(PluginAssistantAudit audit)
+ {
+ var audits = this.SettingsManager.ConfigurationData.AssistantPluginAudits;
+ var existingIndex = audits.FindIndex(x => x.PluginId == audit.PluginId);
+ if (existingIndex >= 0)
+ audits[existingIndex] = audit;
+ else
+ audits.Add(audit);
+ }
+
+ protected override async Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
+ {
+ if (triggeredEvent is Event.PLUGINS_RELOADED)
+ await this.TryAutoAuditAssistantsAsync();
+
+ if (triggeredEvent is Event.CONFIGURATION_CHANGED or Event.PLUGINS_RELOADED)
+ await this.InvokeAsync(this.StateHasChanged);
+ }
+}
diff --git a/app/MindWork AI Studio/Pages/Plugins.razor b/app/MindWork AI Studio/Pages/Plugins.razor
index c1012744..26167b11 100644
--- a/app/MindWork AI Studio/Pages/Plugins.razor
+++ b/app/MindWork AI Studio/Pages/Plugins.razor
@@ -1,4 +1,5 @@
@using AIStudio.Tools.PluginSystem
+@using AIStudio.Tools.PluginSystem.Assistants
@inherits MSGComponentBase
@attribute [Route(Routes.PLUGINS)]
@@ -64,19 +65,25 @@
+ @if (context.Type is PluginType.ASSISTANT)
+ {
+ var assistantPlugin = PluginFactory.RunningPlugins.OfType().FirstOrDefault(x => x.Id == context.Id);
+
+ }
@if (context is { IsInternal: false, Type: not PluginType.CONFIGURATION })
{
var isEnabled = this.SettingsManager.IsPluginEnabled(context);
-
-
+ var activationSwitchDisabled = this.IsActivationSwitchDisabled(context, isEnabled);
+
+
}
-
+
@if (context is { IsInternal: false } && !string.IsNullOrWhiteSpace(context.SourceURL))
{
var sourceUrl = context.SourceURL;
var isSendingMail = IsSendingMail(sourceUrl);
- if(isSendingMail)
+ if (isSendingMail)
{
diff --git a/app/MindWork AI Studio/Pages/Plugins.razor.cs b/app/MindWork AI Studio/Pages/Plugins.razor.cs
index 36de6366..914a13b7 100644
--- a/app/MindWork AI Studio/Pages/Plugins.razor.cs
+++ b/app/MindWork AI Studio/Pages/Plugins.razor.cs
@@ -1,7 +1,12 @@
using AIStudio.Components;
+using AIStudio.Agents.AssistantAudit;
+using AIStudio.Dialogs;
+using AIStudio.Settings.DataModel;
+using AIStudio.Tools.PluginSystem.Assistants;
using AIStudio.Tools.PluginSystem;
using Microsoft.AspNetCore.Components;
+using DialogOptions = AIStudio.Dialogs.DialogOptions;
namespace AIStudio.Pages;
@@ -10,9 +15,18 @@ public partial class Plugins : MSGComponentBase
private const string GROUP_ENABLED = "Enabled";
private const string GROUP_DISABLED = "Disabled";
private const string GROUP_INTERNAL = "Internal";
+ private bool isAutoAuditing;
+
+ private DataAssistantPluginAudit AssistantPluginAuditSettings => this.SettingsManager.ConfigurationData.AssistantPluginAudit;
private TableGroupDefinition groupConfig = null!;
+ [Inject]
+ private IDialogService DialogService { get; init; } = null!;
+
+ [Inject]
+ private AssistantPluginAuditService AssistantPluginAuditService { get; init; } = null!;
+
#region Overrides of ComponentBase
protected override async Task OnInitializedAsync()
@@ -37,21 +51,192 @@ public partial class Plugins : MSGComponentBase
await base.OnInitializedAsync();
}
+ protected override async Task OnAfterRenderAsync(bool firstRender)
+ {
+ if (firstRender)
+ await this.TryAutoAuditAssistantsAsync();
+ }
+
#endregion
private async Task PluginActivationStateChanged(IPluginMetadata pluginMeta)
{
if (this.SettingsManager.IsPluginEnabled(pluginMeta))
+ {
this.SettingsManager.ConfigurationData.EnabledPlugins.Remove(pluginMeta.Id);
- else
+ await this.SettingsManager.StoreSettings();
+ await this.MessageBus.SendMessage(this, Event.CONFIGURATION_CHANGED);
+ return;
+ }
+
+ if (pluginMeta.Type is not PluginType.ASSISTANT)
+ {
this.SettingsManager.ConfigurationData.EnabledPlugins.Add(pluginMeta.Id);
-
+ await this.SettingsManager.StoreSettings();
+ await this.MessageBus.SendMessage(this, Event.CONFIGURATION_CHANGED);
+ return;
+ }
+
+ var assistantPlugin = PluginFactory.RunningPlugins.OfType().FirstOrDefault(x => x.Id == pluginMeta.Id);
+ if (assistantPlugin is null)
+ return;
+
+ var securityState = PluginAssistantSecurityResolver.Resolve(this.SettingsManager, assistantPlugin);
+ if (securityState.RequiresAudit)
+ {
+ await this.OpenAssistantAuditDialogAsync(pluginMeta.Id);
+ return;
+ }
+
+ if (securityState.IsBelowMinimum && securityState.IsBlocked)
+ {
+ var blockedAudit = securityState.Audit;
+ if (blockedAudit is not null)
+ await this.DialogService.ShowMessageBox(this.T("Assistant Audit"), $"{blockedAudit.Level.GetName()}: {blockedAudit.Summary}", this.T("Close"));
+ return;
+ }
+
+ if (securityState.IsBelowMinimum && securityState.CanOverride &&
+ !await this.ConfirmActivationBelowMinimumAsync(pluginMeta.Name, securityState.Audit!.Level))
+ {
+ return;
+ }
+
+ this.SettingsManager.ConfigurationData.EnabledPlugins.Add(pluginMeta.Id);
await this.SettingsManager.StoreSettings();
await this.MessageBus.SendMessage(this, Event.CONFIGURATION_CHANGED);
}
+
+ private async Task OpenAssistantAuditDialogAsync(Guid pluginId)
+ {
+ var parameters = new DialogParameters
+ {
+ { x => x.PluginId, pluginId },
+ };
+ var dialog = await this.DialogService.ShowAsync(this.T("Assistant Audit"), parameters, DialogOptions.FULLSCREEN);
+ var result = await dialog.Result;
+ if (result is null || result.Canceled || result.Data is not AssistantPluginAuditDialogResult auditResult)
+ return;
+
+ if (auditResult.Audit is not null)
+ this.UpsertAuditCard(auditResult.Audit);
+
+ if (auditResult.ActivatePlugin)
+ this.SettingsManager.ConfigurationData.EnabledPlugins.Add(pluginId);
+
+ await this.SettingsManager.StoreSettings();
+ await this.MessageBus.SendMessage(this, Event.CONFIGURATION_CHANGED);
+ }
+
+ private async Task ConfirmActivationBelowMinimumAsync(string pluginName, AssistantAuditLevel actualLevel)
+ {
+ var dialogParameters = new DialogParameters
+ {
+ {
+ x => x.Message,
+ string.Format(
+ this.T("The assistant plugin \"{0}\" was audited with the level \"{1}\", which is below the required minimum level \"{2}\". Your current settings allow activation anyway, but this may be potentially dangerous. Do you really want to enable this plugin?"),
+ pluginName,
+ actualLevel.GetName(),
+ this.AssistantPluginAuditSettings.MinimumLevel.GetName())
+ },
+ };
+
+ var dialogReference = await this.DialogService.ShowAsync(this.T("Potentially Dangerous Plugin"), dialogParameters,
+ DialogOptions.FULLSCREEN);
+ var dialogResult = await dialogReference.Result;
+ return dialogResult is not null && !dialogResult.Canceled;
+ }
+ private bool IsActivationSwitchDisabled(IPluginMetadata pluginMeta, bool isEnabled)
+ {
+ if (isEnabled || pluginMeta.Type is not PluginType.ASSISTANT)
+ return false;
+
+ var assistantPlugin = this.TryGetAssistantPlugin(pluginMeta.Id);
+ if (assistantPlugin is null)
+ return false;
+
+ var securityState = PluginAssistantSecurityResolver.Resolve(this.SettingsManager, assistantPlugin);
+ return securityState.IsBlocked && !securityState.RequiresAudit;
+ }
+
+ private string GetActivationTooltip(IPluginMetadata pluginMeta, bool isEnabled)
+ {
+ if (isEnabled)
+ return this.T("Disable plugin");
+
+ if (pluginMeta.Type is not PluginType.ASSISTANT)
+ return this.T("Enable plugin");
+
+ var assistantPlugin = this.TryGetAssistantPlugin(pluginMeta.Id);
+ if (assistantPlugin is null)
+ return this.T("Enable plugin");
+
+ var securityState = PluginAssistantSecurityResolver.Resolve(this.SettingsManager, assistantPlugin);
+ if (securityState.RequiresAudit)
+ return securityState.ActionLabel;
+
+ return securityState.IsBlocked
+ ? securityState.Description
+ : this.T("Enable plugin");
+ }
+
private static bool IsSendingMail(string sourceUrl) => sourceUrl.TrimStart().StartsWith("mailto:", StringComparison.OrdinalIgnoreCase);
+ private PluginAssistants? TryGetAssistantPlugin(Guid pluginId) => PluginFactory.RunningPlugins.OfType().FirstOrDefault(x => x.Id == pluginId);
+
+ private async Task TryAutoAuditAssistantsAsync()
+ {
+ if (this.isAutoAuditing || !this.AssistantPluginAuditSettings.AutomaticallyAuditAssistants)
+ return;
+
+ this.isAutoAuditing = true;
+
+ try
+ {
+ var wasConfigurationChanged = false;
+ var assistantPlugins = PluginFactory.RunningPlugins.OfType().ToList();
+ foreach (var assistantPlugin in assistantPlugins)
+ {
+ var securityState = PluginAssistantSecurityResolver.Resolve(this.SettingsManager, assistantPlugin);
+ if (!securityState.RequiresAudit)
+ continue;
+
+ var audit = await this.AssistantPluginAuditService.RunAuditAsync(assistantPlugin);
+ if (audit.Level is AssistantAuditLevel.UNKNOWN)
+ {
+ await MessageBus.INSTANCE.SendError(new (Icons.Material.Filled.SettingsSuggest, string.Format(this.T("The automatic security audit for the assistant plugin '{0}' failed. Please run it manually."), assistantPlugin.Name)));
+ continue;
+ }
+
+ this.UpsertAuditCard(audit);
+ wasConfigurationChanged = true;
+ }
+
+ if (!wasConfigurationChanged)
+ return;
+
+ await this.SettingsManager.StoreSettings();
+ await this.MessageBus.SendMessage(this, Event.CONFIGURATION_CHANGED);
+ }
+ finally
+ {
+ this.isAutoAuditing = false;
+ await this.InvokeAsync(this.StateHasChanged);
+ }
+ }
+
+ private void UpsertAuditCard(PluginAssistantAudit audit)
+ {
+ var audits = this.SettingsManager.ConfigurationData.AssistantPluginAudits;
+ var existingIndex = audits.FindIndex(x => x.PluginId == audit.PluginId);
+ if (existingIndex >= 0)
+ audits[existingIndex] = audit;
+ else
+ audits.Add(audit);
+ }
+
#region Overrides of MSGComponentBase
protected override async Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
@@ -59,6 +244,11 @@ public partial class Plugins : MSGComponentBase
switch (triggeredEvent)
{
case Event.PLUGINS_RELOADED:
+ await this.TryAutoAuditAssistantsAsync();
+ await this.InvokeAsync(this.StateHasChanged);
+ break;
+
+ case Event.CONFIGURATION_CHANGED:
await this.InvokeAsync(this.StateHasChanged);
break;
}
diff --git a/app/MindWork AI Studio/Pages/Settings.razor b/app/MindWork AI Studio/Pages/Settings.razor
index 70201807..af89b157 100644
--- a/app/MindWork AI Studio/Pages/Settings.razor
+++ b/app/MindWork AI Studio/Pages/Settings.razor
@@ -29,6 +29,7 @@
}
+
-
\ No newline at end of file
+