finished audit dialog

This commit is contained in:
nilsk 2026-03-26 22:34:39 +01:00
parent edfe6aa046
commit dbe4ece333
11 changed files with 1433 additions and 103 deletions

View File

@ -1,3 +1,4 @@
using System.Text;
using System.Text.Json;
using AIStudio.Chat;
using AIStudio.Provider;
@ -20,9 +21,9 @@ public sealed class AssistantAuditAgent(ILogger<AssistantAuditAgent> logger, ILo
protected override string JobDescription =>
"""
You audit Lua-based newly installed or updated assistant plugins in-depth for security risks in private and enterprise environments.
The Lua code is parsed into functional assistants that help users with various tasks, like coding, e-mails, translations
The Lua code is parsed into functional assistants that help users with various tasks, like coding, emails, translations
and now everything that plugin devs develop. Assistants have a system prompt that is set once and sanitized by us with a security pre- and postamble.
The user prompt is build dynamically at submit and consists of user prompt context followed by the actual user input (Text, Decisions, Time and Date, File and Web content etc.)
The user prompt is built dynamically when the assistant is submitted and consists of user prompt context followed by the actual user input (text, decisions, time and date, file and web content, etc.)
You analyze the plugin manifest code, the assistants' system prompt, the simulated user prompt,
and the list of UI components. The simulated user prompt may contain empty, null-like, or
placeholder values. Treat these placeholders as intentional audit input and focus on prompt
@ -92,7 +93,7 @@ public sealed class AssistantAuditAgent(ILogger<AssistantAuditAgent> logger, ILo
var provider = this.ResolveProvider();
if (provider == AIStudio.Settings.Provider.NONE)
{
await MessageBus.INSTANCE.SendError(new (Icons.Material.Filled.SettingsSuggest, string.Format(TB("No provider is configured for Security Audit-Agent."))));
await MessageBus.INSTANCE.SendError(new (Icons.Material.Filled.SettingsSuggest, string.Format(TB("No provider is configured for the Security Audit Agent."))));
return new AssistantAuditResult
{
@ -104,6 +105,7 @@ public sealed class AssistantAuditAgent(ILogger<AssistantAuditAgent> logger, ILo
logger.LogInformation($"The assistant plugin audit agent uses the provider '{provider.InstanceName}' ({provider.UsedLLMProvider.ToName()}, confidence={provider.UsedLLMProvider.GetConfidence(this.SettingsManager).Level.GetName()}).");
var promptPreview = await plugin.BuildAuditPromptPreviewAsync(token);
var luaManifest = FormatLuaManifest(plugin.ReadAllLuaFiles());
var userPrompt = $$"""
Audit this assistant plugin.
@ -115,7 +117,7 @@ public sealed class AssistantAuditAgent(ILogger<AssistantAuditAgent> logger, ILo
Assistant system prompt:
```
{{plugin.SystemPrompt}}
{{plugin.RawSystemPrompt}}
```
Simulated user prompt preview:
@ -130,7 +132,7 @@ public sealed class AssistantAuditAgent(ILogger<AssistantAuditAgent> logger, ILo
Lua manifest:
```lua
{{plugin.ReadManifestCode()}}
{{luaManifest}}
```
""";
@ -148,7 +150,7 @@ public sealed class AssistantAuditAgent(ILogger<AssistantAuditAgent> logger, ILo
if (response.Content is not ContentText content || string.IsNullOrWhiteSpace(content.Text))
{
logger.LogWarning($"The assistant plugin audit agent did not return text: {response}");
await MessageBus.INSTANCE.SendWarning(new (Icons.Material.Filled.PendingActions, string.Format(TB("The Security Audit was unsuccessful, because the LLMs response was unusable. The Audit Level remains Unknown, so please try again later."))));
await MessageBus.INSTANCE.SendWarning(new (Icons.Material.Filled.PendingActions, string.Format(TB("The security check could not be completed because the LLM's response was unusable. The audit level remains Unknown, so please try again later."))));
return new AssistantAuditResult
{
@ -210,4 +212,24 @@ public sealed class AssistantAuditAgent(ILogger<AssistantAuditAgent> logger, ILo
return [];
}
private static string FormatLuaManifest(IReadOnlyDictionary<string, string> luaFiles)
{
if (luaFiles.Count == 0)
return string.Empty;
var builder = new StringBuilder();
foreach (var luaFile in luaFiles.OrderBy(file => file.Key, StringComparer.Ordinal))
{
if (builder.Length > 0)
builder.AppendLine().AppendLine();
builder.Append("-- File: ");
builder.AppendLine(luaFile.Key);
builder.AppendLine(luaFile.Value);
}
return builder.ToString().TrimEnd();
}
}

View File

@ -1,9 +1,34 @@
using System.Text.Json.Serialization;
namespace AIStudio.Agents.AssistantAudit;
public sealed class AssistantAuditFinding
{
private readonly AssistantAuditLevel severity = AssistantAuditLevel.UNKNOWN;
[JsonIgnore]
public AssistantAuditLevel Severity => this.severity;
[JsonPropertyName("severity")]
public string SeverityText
{
get => this.Severity switch
{
AssistantAuditLevel.DANGEROUS => "critical",
AssistantAuditLevel.CAUTION => "medium",
AssistantAuditLevel.SAFE => "low",
_ => "unknown",
};
init => this.severity = value?.Trim().ToLowerInvariant() switch
{
"critical" => AssistantAuditLevel.DANGEROUS,
"medium" => AssistantAuditLevel.CAUTION,
"low" => AssistantAuditLevel.SAFE,
_ => AssistantAuditLevel.UNKNOWN,
};
}
public string Category { get; init; } = string.Empty;
public string Location { get; init; } = string.Empty;
public string Description { get; init; } = string.Empty;
public string Recommendation { get; init; } = string.Empty;
}

View File

@ -46,11 +46,11 @@ LANG_NAME = "English (United States)"
UI_TEXT_CONTENT = {}
-- The Security Audit was unsuccessful, because the LLMs response was unusable. The Audit Level remains Unknown, so please try again later.
UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T2113359519"] = "The Security Audit was unsuccessful, because the LLMs response was unusable. The Audit Level remains Unknown, so please try again later."
-- The security check could not be completed because the LLM's response was unusable. The audit level remains Unknown, so please try again later.
UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T2451573087"] = "The security check could not be completed because the LLM's response was unusable. The audit level remains Unknown, so please try again later."
-- No provider is configured for Security Audit-Agent.
UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T4000913009"] = "No provider is configured for Security Audit-Agent."
-- No provider is configured for the Security Audit Agent.
UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T3605554201"] = "No provider is configured for the Security Audit Agent."
-- Needs Review
UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITLEVELEXTENSIONS::T1114911302"] = "Needs Review"
@ -2935,17 +2935,74 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T474393241"] = "Please select
-- Delete Workspace
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T701874671"] = "Delete Workspace"
-- Entries: {0}
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1098127509"] = "Entries: {0}"
-- {0:0.##} GB
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1224874808"] = "{0:0.##} GB"
-- Potentially Dangerous Plugin
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1229643769"] = "Potentially Dangerous Plugin"
-- Plugin root
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1303883002"] = "Plugin root"
-- Last modified
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1310524248"] = "Last modified"
-- Count: {0}
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T131135808"] = "Count: {0}"
-- {0:0.##} MB
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1357418474"] = "{0:0.##} MB"
-- No security issues were found during this check.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1423034104"] = "No security issues were found during this check."
-- No provider configured
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1476185409"] = "No provider configured"
-- {0:0.##} KB
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T14914764"] = "{0:0.##} KB"
-- Prompt: empty
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1533307170"] = "Prompt: empty"
-- This plugin is below the required safety level. Your settings still allow activation, but enabling it requires an extra confirmation because it may be unsafe.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1539381299"] = "This plugin is below the required safety level. Your settings still allow activation, but enabling it requires an extra confirmation because it may be unsafe."
-- Components
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1550582665"] = "Components"
-- Created
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T165548891"] = "Created"
-- Lua Manifest
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T165738710"] = "Lua Manifest"
-- Required minimum level
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1862086522"] = "Required minimum level"
-- Enable Assistant Plugin
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1676241565"] = "Enable Assistant Plugin"
-- Unknown plugin
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1834795216"] = "Unknown plugin"
-- This plugin cannot be activated because its audit result is below the required safety level and your settings block activation in this case.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1839656215"] = "This plugin cannot be activated because its audit result is below the required safety level and your settings block activation in this case."
-- Children: {0}
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T193192210"] = "Children: {0}"
-- null
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1996966820"] = "null"
-- Properties
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2177370620"] = "Properties"
-- Items: {0}
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2204150657"] = "Items: {0}"
-- {0} B
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2562655035"] = "{0} B"
-- The assistant plugin could not be resolved for auditing.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T273798258"] = "The assistant plugin could not be resolved for auditing."
@ -2953,17 +3010,50 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T273798258"] = "
-- Audit provider
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2757790517"] = "Audit provider"
-- Enable Plugin
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3233590741"] = "Enable Plugin"
-- Size
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2789707388"] = "Size"
-- Prompt: set
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3156437951"] = "Prompt: set"
-- Findings
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3224848879"] = "Findings"
-- {0} | Last modified {1}
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3350447013"] = "{0} | Last modified {1}"
-- The assistant plugin \"{0}\" was audited with the level \"{1}\", which is below the required safety level \"{2}\". Your current settings still allow activation, but this may be unsafe. Do you really want to enable this plugin?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3418077666"] = "The assistant plugin \\\"{0}\\\" was audited with the level \\\"{1}\\\", which is below the required safety level \\\"{2}\\\". Your current settings still allow activation, but this may be unsafe. Do you really want to enable this plugin?"
-- Unknown
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3424652889"] = "Unknown"
-- Close
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3448155331"] = "Close"
-- The audit uses a simulated prompt preview. Empty or placeholder values in the preview are expected during this security check.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T439841458"] = "The audit uses a simulated prompt preview. Empty or placeholder values in the preview are expected during this security check."
-- Value
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3511155050"] = "Value"
-- Run Audit
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T564725977"] = "Run Audit"
-- Last accessed
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3579946376"] = "Last accessed"
-- Unknown key
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3647690370"] = "Unknown key"
-- Minimum required safety level
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3652671056"] = "Minimum required safety level"
-- Unavailable
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3662391977"] = "Unavailable"
-- Plugin Structure
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T371537943"] = "Plugin Structure"
-- Audit Result
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3844960449"] = "Audit Result"
-- empty
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T413646574"] = "empty"
-- Prompt Preview
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T576347259"] = "Prompt Preview"
@ -2971,6 +3061,15 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T576347259"] = "
-- System Prompt
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T628396066"] = "System Prompt"
-- This security check uses a sample prompt preview. Empty or placeholder values in the preview are expected.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T737998363"] = "This security check uses a sample prompt preview. Empty or placeholder values in the preview are expected."
-- Safe
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T760494712"] = "Safe"
-- Start Security Check
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T811648299"] = "Start Security Check"
-- Cancel
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T900713019"] = "Cancel"
@ -5773,6 +5872,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T870640199"] = "For some data tra
-- Install Pandoc
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T986578435"] = "Install Pandoc"
-- Potentially Dangerous Plugin
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1229643769"] = "Potentially Dangerous Plugin"
-- Disable plugin
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1430375822"] = "Disable plugin"
@ -5794,6 +5896,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T2057806005"] = "Enable plugin"
-- Plugins
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T2222816203"] = "Plugins"
-- 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?
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T2531356312"] = "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?"
-- Enabled Plugins
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T2738444034"] = "Enabled Plugins"
@ -6529,6 +6634,81 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T3290596792"] = "Error during Mi
-- Microsoft Word export successful
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T4256043333"] = "Microsoft Word export successful"
-- Stack
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T135058847"] = "Stack"
-- Button group
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T1392576058"] = "Button group"
-- Profiles Selection
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T1437749589"] = "Profiles Selection"
-- Image
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T1494001562"] = "Image"
-- Text Area
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T1593629311"] = "Text Area"
-- File Content
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T165091006"] = "File Content"
-- Grid Item
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T1991378436"] = "Grid Item"
-- List
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T2368288673"] = "List"
-- Provider Selection
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T268262394"] = "Provider Selection"
-- Root
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T2703841893"] = "Root"
-- Container
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T2990360344"] = "Container"
-- Accordion
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T3372988345"] = "Accordion"
-- Date Range
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T3400615792"] = "Date Range"
-- Plane Text
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T3557717784"] = "Plane Text"
-- Switch
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T3656636817"] = "Switch"
-- Time
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T3756319748"] = "Time"
-- Dropdown
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T3829804792"] = "Dropdown"
-- Color
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T3853794552"] = "Color"
-- Accordion Section
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T4180733902"] = "Accordion Section"
-- Heading
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T4231005109"] = "Heading"
-- Unknown Element
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T434854509"] = "Unknown Element"
-- Web Content
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T590188228"] = "Web Content"
-- Grid
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T800286385"] = "Grid"
-- Button
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T864557713"] = "Button"
-- Date
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T995259257"] = "Date"
-- Failed to parse the UI render tree from the ASSISTANT lua table.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T1318499252"] = "Failed to parse the UI render tree from the ASSISTANT lua table."

View File

@ -0,0 +1,10 @@
namespace AIStudio.Components;
public sealed class AssistantAuditTreeItem : ITreeItem
{
public string Text { get; init; } = string.Empty;
public string Icon { get; init; } = string.Empty;
public string Caption { get; init; } = string.Empty;
public bool Expandable { get; init; }
public bool IsComponent { get; init; } = true;
}

View File

@ -1,4 +1,5 @@
@using AIStudio.Agents.AssistantAudit
@using AIStudio.Components
@inherits MSGComponentBase
<MudDialog DefaultFocus="DefaultFocus.FirstChild">
@ -11,9 +12,9 @@
}
else
{
<MudStack Spacing="2">
<MudAlert Severity="Severity.Info" Dense="true">
@T("The audit uses a simulated prompt preview. Empty or placeholder values in the preview are expected during this security check.")
<MudStack Spacing="2">
<MudAlert Severity="Severity.Info" Dense="true">
@T("This security check uses a sample prompt preview. Empty or placeholder values in the preview are expected.")
</MudAlert>
<MudPaper Class="pa-3 border-dashed border rounded-lg">
@ -23,53 +24,214 @@
@T("Audit provider"): <strong>@this.ProviderLabel</strong>
</MudText>
<MudText Typo="Typo.body2">
@T("Required minimum level"): <strong>@this.MinimumLevelLabel</strong>
@T("Minimum required safety level"): <strong>@this.MinimumLevelLabel</strong>
</MudText>
</MudPaper>
<MudExpansionPanels MultiExpansion="true">
<MudExpansionPanel Text="@T("System Prompt")" Expanded="true">
<MudTextField T="string" Text="@this.plugin.SystemPrompt" ReadOnly="true" Variant="Variant.Outlined" Lines="8" Class="mt-2" />
<MudExpansionPanel Expanded="true">
<TitleContent>
<div class="d-flex">
<MudIcon Icon="@Icons.Material.Filled.EditNote" class="mr-3" Color="Color.Primary"></MudIcon>
<MudText>@T("System Prompt")</MudText>
</div>
</TitleContent>
<ChildContent>
<MudTextField T="string" Text="@this.plugin.RawSystemPrompt" ReadOnly="true" Variant="Variant.Outlined" Lines="8" Class="mt-2"/>
</ChildContent>
</MudExpansionPanel>
<MudExpansionPanel Text="@T("Prompt Preview")" Expanded="true">
<MudTextField T="string" Text="@this.promptPreview" ReadOnly="true" Variant="Variant.Outlined" Lines="8" Class="mt-2" />
<MudExpansionPanel Expanded="false">
<TitleContent>
<div class="d-flex">
<MudIcon Icon="@Icons.Material.Filled.Preview" class="mr-3" Color="Color.Primary"></MudIcon>
<MudText>@T("Prompt Preview")</MudText>
</div>
</TitleContent>
<ChildContent>
<MudTextField T="string" Text="@this.promptPreview" ReadOnly="true" Variant="Variant.Outlined" Lines="8" Class="mt-2"/>
</ChildContent>
</MudExpansionPanel>
<MudExpansionPanel Text="@T("Components")">
<MudTextField T="string" Text="@this.componentSummary" ReadOnly="true" Variant="Variant.Outlined" Lines="10" Class="mt-2" />
<MudExpansionPanel KeepContentAlive="false">
<TitleContent>
<div class="d-flex">
<MudIcon Icon="@Icons.Material.Filled.AccountTree" class="mr-3" Color="Color.Primary"></MudIcon>
<MudText>@T("Components")</MudText>
</div>
</TitleContent>
<ChildContent>
<MudTreeView T="ITreeItem" Items="@this.componentTreeItems" ReadOnly="true" Hover="true" Dense="true" Disabled="false" ExpandOnClick="true" Class="mt-3">
<ItemTemplate Context="item">
@if (item.Value is AssistantAuditTreeItem treeItem)
{
<MudTreeViewItem T="ITreeItem" Icon="@treeItem.Icon" Value="@item.Value" Expanded="@item.Expanded" CanExpand="@treeItem.Expandable" Items="@item.Children">
<BodyContent>
<div style="display: grid; grid-template-columns: 1fr auto; align-items: center; width: 100%">
<MudText Style="justify-self: start;">
@treeItem.Text
</MudText>
@if (!string.IsNullOrWhiteSpace(treeItem.Caption))
{
if (treeItem.IsComponent)
{
<MudText Typo="Typo.caption" Color="Color.Secondary" Style="justify-self: end;">
@treeItem.Caption
</MudText>
}
else
{
<MudText Typo="Typo.overline" Color="Color.Primary" Style="justify-self: end;">
@treeItem.Caption
</MudText>
}
}
</div>
</BodyContent>
</MudTreeViewItem>
}
</ItemTemplate>
</MudTreeView>
</ChildContent>
</MudExpansionPanel>
<MudExpansionPanel Text="@T("Lua Manifest")">
<MudTextField T="string" Text="@this.luaCode" ReadOnly="true" Variant="Variant.Outlined" Lines="18" Class="mt-2" />
<MudExpansionPanel KeepContentAlive="false">
<TitleContent>
<div class="d-flex">
<MudIcon Icon="@Icons.Material.Filled.FolderZip" class="mr-3" Color="Color.Primary"></MudIcon>
<MudText>@T("Plugin Structure")</MudText>
</div>
</TitleContent>
<ChildContent>
<MudTreeView T="ITreeItem" Items="@this.fileSystemTreeItems" ReadOnly="true" Hover="true" Dense="true" Disabled="false" ExpandOnClick="true" Class="mt-3">
<ItemTemplate Context="item">
@if (item.Value is AssistantAuditTreeItem treeItem)
{
<MudTreeViewItem T="ITreeItem" Icon="@treeItem.Icon" Value="@item.Value" Expanded="@item.Expanded" CanExpand="@treeItem.Expandable" Items="@item.Children">
<BodyContent>
<div style="display: grid; grid-template-columns: 1fr auto; align-items: center; width: 100%">
<MudText Style="justify-self: start;">
@treeItem.Text
</MudText>
@if (!string.IsNullOrWhiteSpace(treeItem.Caption))
{
<MudText Typo="Typo.caption" Color="Color.Secondary" Style="justify-self: end;">
@treeItem.Caption
</MudText>
}
</div>
</BodyContent>
</MudTreeViewItem>
}
</ItemTemplate>
</MudTreeView>
</ChildContent>
</MudExpansionPanel>
<MudExpansionPanel KeepContentAlive="false">
<TitleContent>
<div class="d-flex">
<MudIcon Icon="@Icons.Material.Filled.Code" class="mr-3" Color="Color.Primary"></MudIcon>
<MudText>@T("Lua Manifest")</MudText>
</div>
</TitleContent>
<ChildContent>
<MudExpansionPanels Elevation="0" Dense="true">
@foreach (var file in this.luaFiles)
{
var fileInfo = new FileInfo(Path.Combine(this.plugin.PluginPath, file.Key));
<MudExpansionPanel Expanded="false" Icon="@Icons.Material.Outlined.ArrowDropDown">
<TitleContent>
<div class="d-flex align-center justify-start">
<MudTooltip Placement="Placement.Left" Arrow="true">
<ChildContent>
<MudIconButton Icon="@Icons.Material.Outlined.Info" Size="Size.Small" Color="Color.Info" Class="mr-1"/>
</ChildContent>
<TooltipContent>
<MudPaper Class="pa-3" >
<MudStack Spacing="1">
<MudText Typo="Typo.subtitle2">@file.Key</MudText>
<MudDivider/>
<MudText Typo="Typo.body2">@T("Size"): @this.FormatFileSize(fileInfo.Length)</MudText>
<MudText Typo="Typo.body2">@T("Created"): @this.FormatFileTimestamp(fileInfo.CreationTime)</MudText>
<MudText Typo="Typo.body2">@T("Last accessed"): @this.FormatFileTimestamp(fileInfo.LastAccessTime)</MudText>
<MudText Typo="Typo.body2">@T("Last modified"): @this.FormatFileTimestamp(fileInfo.LastWriteTime)</MudText>
</MudStack>
</MudPaper>
</TooltipContent>
</MudTooltip>
<MudText Class="">@file.Key</MudText>
</div>
</TitleContent>
<ChildContent>
<MudTextField T="string" Text="@file.Value" ReadOnly="true" Variant="Variant.Outlined" Lines="25" Class="mt-2" Style="font-family: monospace"/>
</ChildContent>
</MudExpansionPanel>
}
</MudExpansionPanels>
</ChildContent>
</MudExpansionPanel>
</MudExpansionPanels>
@if (this.audit is not null)
{
<MudAlert Severity="@this.audit.Level.GetSeverity()" Dense="true">
<strong>@this.audit.Level.GetName()</strong>: @this.audit.Summary
</MudAlert>
<MudStack Spacing="2" Class="mt-4">
<MudText Typo="Typo.h6">@T("Audit Result")</MudText>
@if (this.audit.Findings.Count > 0)
{
<MudList T="string" Dense="true" Class="border rounded-lg">
@foreach (var finding in this.audit.Findings)
@if (this.audit.Findings.Count == 0 && this.audit.Level is not AssistantAuditLevel.UNKNOWN)
{
<MudAlert Severity="Severity.Success" Variant="Variant.Filled" Dense="true" Icon="@Icons.Material.Filled.VerifiedUser">
<strong>@T("Safe")</strong><span>: @T("No security issues were found during this check.")</span>
</MudAlert>
}
else
{
<MudAlert Severity="@this.GetAuditResultSeverity()" Variant="Variant.Filled" Dense="true">
<strong>@this.audit.Level.GetName()</strong><span>: @this.audit.Summary</span>
</MudAlert>
@if (this.IsActivationBlockedBySettings)
{
<MudListItem T="string">
<div>
<strong>@finding.Category</strong>
@if (!string.IsNullOrWhiteSpace(finding.Location))
{
<span> (@finding.Location)</span>
}
<div>@finding.Description</div>
@if (!string.IsNullOrWhiteSpace(finding.Recommendation))
{
<div><em>@finding.Recommendation</em></div>
}
</div>
</MudListItem>
<MudAlert Severity="Severity.Error" Variant="Variant.Text" Dense="true" Icon="@Icons.Material.Filled.Block">
@T("This plugin cannot be activated because its audit result is below the required safety level and your settings block activation in this case.")
</MudAlert>
}
</MudList>
}
else if (this.RequiresActivationConfirmation)
{
<MudAlert Severity="Severity.Warning" Variant="Variant.Text" Dense="true" Icon="@Icons.Material.Filled.WarningAmber">
@T("This plugin is below the required safety level. Your settings still allow activation, but enabling it requires an extra confirmation because it may be unsafe.")
</MudAlert>
}
<MudText Typo="Typo.subtitle2">@T("Findings")</MudText>
<MudStack Spacing="2">
@foreach (var finding in this.audit.Findings)
{
<MudAlert Severity="@this.GetFindingSeverity(finding.Severity)"
Variant="Variant.Text"
Dense="true"
Icon="@this.GetFindingIcon(finding.Severity)"
Class="pa-3"
Elevation="3">
<MudStack Spacing="1" Class="mt-n2">
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.subtitle2">@finding.Category</MudText>
<MudChip T="string"
Variant="Variant.Text"
Color="@this.GetFindingColor(finding.Severity)"
Size="Size.Small">
@finding.Severity.GetName()
</MudChip>
</MudStack>
@if (!string.IsNullOrEmpty(finding.Location))
{
<MudText Typo="Typo.caption">@finding.Location</MudText>
}
<MudText Typo="Typo.body2">@finding.Description</MudText>
</MudStack>
</MudAlert>
}
</MudStack>
}
</MudStack>
}
</MudStack>
}
@ -79,10 +241,13 @@
@(this.audit is null ? T("Cancel") : T("Close"))
</MudButton>
<MudButton OnClick="@this.RunAudit" Variant="Variant.Filled" Color="Color.Primary" Disabled="@(!this.CanRunAudit)">
@T("Run Audit")
</MudButton>
<MudButton OnClick="@this.EnablePlugin" Variant="Variant.Filled" Color="@this.EnableButtonColor" Disabled="@(!this.CanEnablePlugin)">
@T("Enable Plugin")
@T("Start Security Check")
</MudButton>
@if (this.CanEnablePlugin)
{
<MudButton OnClick="@this.EnablePlugin" Variant="Variant.Filled" Color="@this.EnableButtonColor">
@T("Enable Assistant Plugin")
</MudButton>
}
</DialogActions>
</MudDialog>

View File

@ -1,48 +1,85 @@
using AIStudio.Agents.AssistantAudit;
using AIStudio.Components;
using AIStudio.Provider;
using AIStudio.Settings.DataModel;
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
{
[CascadingParameter]
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(AssistantPluginAuditDialog).Namespace,
nameof(AssistantPluginAuditDialog));
[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = null!;
[Inject]
private AssistantAuditAgent AuditAgent { get; init; } = null!;
[Inject]
private IDialogService DialogService { get; init; } = null!;
[Parameter]
public Guid PluginId { get; set; }
[Parameter] public Guid PluginId { get; set; }
private PluginAssistants? plugin;
private PluginAssistantAudit? audit;
private string promptPreview = string.Empty;
private string componentSummary = string.Empty;
private string luaCode = string.Empty;
private ImmutableDictionary<string, string> luaFiles = ImmutableDictionary.Create<string, string>();
private IReadOnlyCollection<TreeItemData<ITreeItem>> componentTreeItems = [];
private IReadOnlyCollection<TreeItemData<ITreeItem>> fileSystemTreeItems = [];
private CultureInfo fileInfoCulture = CultureInfo.InvariantCulture;
private bool isAuditing;
private AIStudio.Settings.Provider CurrentProvider => this.SettingsManager.GetPreselectedProvider(Tools.Components.AGENT_ASSISTANT_PLUGIN_AUDIT, null, true);
private string ProviderLabel => this.CurrentProvider == AIStudio.Settings.Provider.NONE
? this.T("No provider configured")
: $"{this.CurrentProvider.InstanceName} ({this.CurrentProvider.UsedLLMProvider.ToName()})";
private DataAssistantPluginAudit AuditSettings => this.SettingsManager.ConfigurationData.AssistantPluginAudit;
private AssistantAuditLevel MinimumLevel => this.SettingsManager.ConfigurationData.AssistantPluginAudit.MinimumLevel;
private string MinimumLevelLabel => this.MinimumLevel.GetName();
private bool CanRunAudit => this.plugin is not null && this.CurrentProvider != AIStudio.Settings.Provider.NONE && !this.isAuditing;
private bool CanEnablePlugin => this.audit is not null && (this.audit.Level >= this.MinimumLevel || !this.SettingsManager.ConfigurationData.AssistantPluginAudit.BlockActivationBelowMinimum);
private Color EnableButtonColor => this.audit is not null && this.audit.Level >= this.MinimumLevel ? Color.Success : Color.Warning;
private bool CanRunAudit => this.plugin is not null && this.CurrentProvider != AIStudio.Settings.Provider.NONE &&
!this.isAuditing;
private bool IsAuditBelowMinimum => this.audit is not null && this.audit.Level < this.MinimumLevel;
private bool IsActivationBlockedBySettings => this.audit is null || this.IsAuditBelowMinimum && this.AuditSettings.BlockActivationBelowMinimum;
private bool RequiresActivationConfirmation => this.audit is not null && this.IsAuditBelowMinimum && !this.AuditSettings.BlockActivationBelowMinimum;
private bool CanEnablePlugin => this.audit is not null && !this.isAuditing && !this.IsActivationBlockedBySettings;
private Color EnableButtonColor => this.RequiresActivationConfirmation ? Color.Warning : Color.Success;
private const ushort BYTES_PER_KILOBYTE = 1024;
protected override async Task OnInitializedAsync()
{
this.plugin = PluginFactory.RunningPlugins.OfType<PluginAssistants>().FirstOrDefault(x => x.Id == this.PluginId);
var activeLanguagePlugin = await this.SettingsManager.GetActiveLanguagePlugin();
this.fileInfoCulture = this.CreateFileInfoCulture(activeLanguagePlugin.IETFTag);
this.plugin = PluginFactory.RunningPlugins.OfType<PluginAssistants>()
.FirstOrDefault(x => x.Id == this.PluginId);
if (this.plugin is not null)
{
this.promptPreview = await this.plugin.BuildAuditPromptPreviewAsync();
this.componentSummary = this.plugin.CreateAuditComponentSummary();
this.luaCode = this.plugin.ReadManifestCode();
this.componentTreeItems = this.CreateAuditTreeItems(this.plugin.RootComponent);
this.fileSystemTreeItems = this.CreatePluginFileSystemTreeItems(this.plugin.PluginPath);
this.luaFiles = this.plugin.ReadAllLuaFiles();
}
await base.OnInitializedAsync();
@ -65,7 +102,9 @@ public partial class AssistantPluginAuditDialog : MSGComponentBase
PluginHash = this.plugin.ComputeAuditHash(),
AuditedAtUtc = DateTimeOffset.UtcNow,
AuditProviderId = this.CurrentProvider.Id,
AuditProviderName = this.CurrentProvider == AIStudio.Settings.Provider.NONE ? string.Empty : this.CurrentProvider.InstanceName,
AuditProviderName = this.CurrentProvider == AIStudio.Settings.Provider.NONE
? string.Empty
: this.CurrentProvider.InstanceName,
Level = AssistantAuditLevelExtensions.Parse(result.Level),
Summary = result.Summary,
Confidence = result.Confidence,
@ -91,11 +130,408 @@ public partial class AssistantPluginAuditDialog : MSGComponentBase
this.MudDialog.Close(DialogResult.Ok(new AssistantPluginAuditDialogResult(this.audit, false)));
}
private void EnablePlugin()
private async Task EnablePlugin()
{
if (this.audit is null)
return;
if (this.IsActivationBlockedBySettings)
return;
if (this.RequiresActivationConfirmation && !await this.ConfirmActivationBelowMinimumAsync())
return;
this.MudDialog.Close(DialogResult.Ok(new AssistantPluginAuditDialogResult(this.audit, true)));
}
private async Task<bool> ConfirmActivationBelowMinimumAsync()
{
var dialogParameters = new DialogParameters<ConfirmDialog>
{
{
x => x.Message,
string.Format(
T("The assistant plugin \"{0}\" was audited with the level \"{1}\", which is below the required safety level \"{2}\". Your current settings still allow activation, but this may be unsafe. Do you really want to enable this plugin?"),
this.plugin?.Name ?? T("Unknown plugin"),
this.audit?.Level.GetName() ?? T("Unknown"),
this.MinimumLevelLabel)
},
};
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>(T("Potentially Dangerous Plugin"), dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result;
return dialogResult is not null && !dialogResult.Canceled;
}
private Severity GetAuditResultSeverity() => this.audit?.Level switch
{
AssistantAuditLevel.DANGEROUS => Severity.Error,
AssistantAuditLevel.CAUTION => Severity.Warning,
AssistantAuditLevel.SAFE => Severity.Success,
_ => Severity.Normal,
};
private Severity GetFindingSeverity(AssistantAuditLevel severity) => severity switch
{
AssistantAuditLevel.DANGEROUS => Severity.Error,
AssistantAuditLevel.CAUTION => Severity.Warning,
AssistantAuditLevel.SAFE => Severity.Success,
_ => Severity.Normal,
};
private string GetFindingIcon(AssistantAuditLevel severity) => severity switch
{
AssistantAuditLevel.DANGEROUS => Icons.Material.Filled.Dangerous,
AssistantAuditLevel.CAUTION => Icons.Material.Filled.WarningAmber,
AssistantAuditLevel.SAFE => Icons.Material.Filled.Verified,
_ => Icons.Material.Filled.Info,
};
private Color GetFindingColor(AssistantAuditLevel severity) => severity switch
{
AssistantAuditLevel.DANGEROUS => Color.Error,
AssistantAuditLevel.CAUTION => Color.Warning,
AssistantAuditLevel.SAFE => Color.Success,
_ => Color.Default,
};
/// <summary>
/// Creates the full audit tree for the assistant component hierarchy.
/// The dialog owns this mapping because it is pure presentation logic for the audit UI.
/// </summary>
private IReadOnlyCollection<TreeItemData<ITreeItem>> CreateAuditTreeItems(IAssistantComponent? rootComponent)
{
if (rootComponent is null)
return [];
return [this.CreateComponentTreeItem(rootComponent, index: 0, depth: 0)];
}
/// <summary>
/// Maps one assistant component into a tree node and recursively appends its value, props and child components.
/// </summary>
private TreeItemData<ITreeItem> CreateComponentTreeItem(IAssistantComponent component, int index, int depth)
{
var children = new List<TreeItemData<ITreeItem>>();
if (component.Props.TryGetValue("Value", out var value))
children.Add(this.CreateValueTreeItem(TB("Value"), value, depth + 1));
if (component.Props.Count > 0)
children.Add(this.CreatePropsTreeItem(component.Props, depth + 1));
children.AddRange(component.Children.Select((child, childIndex) =>
this.CreateComponentTreeItem(child, childIndex, depth + 1)));
return new TreeItemData<ITreeItem>
{
Expanded = depth < 2,
Expandable = children.Count > 0,
Value = new AssistantAuditTreeItem
{
Text = this.GetComponentTreeItemText(component),
Caption = this.GetComponentTreeItemCaption(component, index),
Icon = component.Type.GetIcon(),
Expandable = children.Count > 0,
},
Children = children,
};
}
/// <summary>
/// Groups all props of a component under a single "Props" branch to keep the component nodes compact.
/// </summary>
private TreeItemData<ITreeItem> CreatePropsTreeItem(IReadOnlyDictionary<string, object> props, int depth)
{
var children = props
.OrderBy(prop => prop.Key, StringComparer.Ordinal)
.Select(prop => this.CreateValueTreeItem(prop.Key, prop.Value, depth + 1))
.ToList();
return new TreeItemData<ITreeItem>
{
Expanded = depth < 2,
Expandable = children.Count > 0,
Value = new AssistantAuditTreeItem
{
Text = TB("Properties"),
Caption = string.Format(TB("Count: {0}"), props.Count),
Icon = Icons.Material.Filled.Code,
Expandable = children.Count > 0,
IsComponent = false,
},
Children = children,
};
}
/// <summary>
/// Converts a scalar or structured prop value into a tree node.
/// Scalars stay on one line, while structured values recursively expose their children.
/// </summary>
private TreeItemData<ITreeItem> CreateValueTreeItem(string label, object? value, int depth)
{
var children = this.CreateValueChildren(value, depth + 1);
return new TreeItemData<ITreeItem>
{
Expanded = depth < 2,
Expandable = children.Count > 0,
Value = new AssistantAuditTreeItem
{
Text = label,
Caption = children.Count == 0 ? this.FormatScalarValue(value) : this.GetStructuredValueCaption(value),
Icon = this.GetValueIcon(value),
Expandable = children.Count > 0,
IsComponent = false,
},
Children = children,
};
}
/// <summary>
/// Recursively expands structured values for the tree.
/// Lists, dictionaries and known DTO-style assistant values become nested tree branches.
/// </summary>
private List<TreeItemData<ITreeItem>> CreateValueChildren(object? value, int depth)
{
if (value is null || IsScalarValue(value))
return [];
if (value is IDictionary dictionary)
return this.CreateDictionaryChildren(dictionary, depth);
if (value is IEnumerable enumerable && value is not string)
return this.CreateEnumerableChildren(enumerable, depth);
return this.CreateObjectChildren(value, depth);
}
private List<TreeItemData<ITreeItem>> CreateDictionaryChildren(IDictionary dictionary, int depth)
{
var children = new List<TreeItemData<ITreeItem>>();
foreach (DictionaryEntry entry in dictionary)
{
var keyText = entry.Key.ToString() ?? TB("Unknown key");
children.Add(this.CreateValueTreeItem(keyText, entry.Value, depth));
}
return children;
}
/// <summary>
/// Creates a tree for the plugin directory so the audit can show unexpected folders and files, while excluding irrelevant dependency folders.
/// </summary>
private IReadOnlyCollection<TreeItemData<ITreeItem>> CreatePluginFileSystemTreeItems(string pluginPath)
{
if (string.IsNullOrWhiteSpace(pluginPath) || !Directory.Exists(pluginPath))
return [];
return [this.CreateDirectoryTreeItem(pluginPath, pluginPath, depth: 0)];
}
private TreeItemData<ITreeItem> CreateDirectoryTreeItem(string directoryPath, string rootPath, int depth)
{
var childDirectories = Directory.EnumerateDirectories(directoryPath)
.OrderBy(path => path, StringComparer.Ordinal)
.Select(path => this.CreateDirectoryTreeItem(path, rootPath, depth + 1))
.ToList();
var childFiles = Directory.EnumerateFiles(directoryPath)
.OrderBy(path => path, StringComparer.Ordinal)
.Select(path => this.CreateFileTreeItem(path, depth + 1))
.ToList();
var children = new List<TreeItemData<ITreeItem>>(childDirectories.Count + childFiles.Count);
children.AddRange(childDirectories);
children.AddRange(childFiles);
var relativePath = Path.GetRelativePath(rootPath, directoryPath);
var displayName = depth == 0
? Path.GetFileName(directoryPath)
: relativePath.Split(Path.DirectorySeparatorChar).Last();
return new TreeItemData<ITreeItem>
{
Expanded = depth < 2,
Expandable = children.Count > 0,
Value = new AssistantAuditTreeItem
{
Text = string.IsNullOrWhiteSpace(displayName) ? directoryPath : displayName,
Caption = depth == 0 ? TB("Plugin root") : string.Format(TB("Items: {0}"), children.Count),
Icon = children.Count > 0 ? Icons.Material.Filled.FolderCopy : Icons.Material.Filled.Folder,
Expandable = children.Count > 0,
IsComponent = false,
},
Children = children,
};
}
private TreeItemData<ITreeItem> CreateFileTreeItem(string filePath, int depth)
{
var fileInfo = new FileInfo(filePath);
return new TreeItemData<ITreeItem>
{
Expanded = depth < 2,
Expandable = false,
Value = new AssistantAuditTreeItem
{
Text = Path.GetFileName(filePath),
Caption = string.Format(TB("{0} | Last modified {1}"), this.FormatFileSize(fileInfo.Length),
this.FormatFileTimestamp(fileInfo.LastWriteTime)),
Icon = this.GetFileIcon(filePath),
Expandable = false,
IsComponent = false,
},
};
}
private string GetFileIcon(string filePath)
{
var extension = Path.GetExtension(filePath);
return extension.ToLowerInvariant() switch
{
".lua" => Icons.Material.Filled.Code,
".md" => Icons.Material.Filled.Article,
".json" => Icons.Material.Filled.DataObject,
".png" or ".jpg" or ".jpeg" or ".svg" or ".webp" => Icons.Material.Filled.Image,
_ => Icons.Material.Filled.InsertDriveFile,
};
}
private List<TreeItemData<ITreeItem>> CreateEnumerableChildren(IEnumerable enumerable, int depth)
{
var children = new List<TreeItemData<ITreeItem>>();
var index = 0;
foreach (var item in enumerable)
{
children.Add(this.CreateValueTreeItem($"[{index}]", item, depth));
index++;
}
return children;
}
/// <summary>
/// Falls back to public instance properties for simple DTO-style values such as dropdown items.
/// Getter failures are treated defensively so the audit dialog never crashes because of a problematic property.
/// </summary>
private List<TreeItemData<ITreeItem>> CreateObjectChildren(object value, int depth)
{
var children = new List<TreeItemData<ITreeItem>>();
foreach (var property in value.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
if (!property.CanRead || property.GetIndexParameters().Length != 0)
continue;
object? propertyValue;
try
{
propertyValue = property.GetValue(value);
}
catch (Exception)
{
propertyValue = TB("Unavailable");
}
children.Add(this.CreateValueTreeItem(property.Name, propertyValue, depth));
}
return children;
}
private string GetComponentTreeItemText(IAssistantComponent component)
{
var type = component.Type.GetDisplayName();
if (component is INamedAssistantComponent named && !string.IsNullOrWhiteSpace(named.Name))
return $"{type}: {named.Name}";
return type;
}
private string GetComponentTreeItemCaption(IAssistantComponent component, int index)
{
var details = new List<string> { $"#{index + 1}" };
if (component is IStatefulAssistantComponent stateful)
details.Add(string.IsNullOrWhiteSpace(stateful.UserPrompt) ? TB("Prompt: empty") : TB("Prompt: set"));
if (component.Children.Count > 0)
details.Add(string.Format(TB("Children: {0}"), component.Children.Count));
return string.Join(" | ", details);
}
private static bool IsScalarValue(object value)
{
return value is string or bool or char or Enum
or byte or sbyte or short or ushort or int or uint or long or ulong
or float or double or decimal
or DateTime or DateTimeOffset or TimeSpan or Guid;
}
private string FormatScalarValue(object? value) => value switch
{
null => TB("null"),
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,
};
private string GetStructuredValueCaption(object? value) => value switch
{
null => TB("null"),
IDictionary dictionary => string.Format(TB("Entries: {0}"), dictionary.Count),
IEnumerable enumerable when value is not string => string.Format(TB("Items: {0}"),
enumerable.Cast<object?>().Count()),
_ => value.GetType().Name,
};
private string GetValueIcon(object? value) => value switch
{
null => Icons.Material.Filled.Block,
bool => Icons.Material.Outlined.ToggleOn,
string => Icons.Material.Outlined.Abc,
int => Icons.Material.Filled.Numbers,
Enum => Icons.Material.Filled.Label,
IDictionary => Icons.Material.Filled.DataObject,
IEnumerable when value is not string => Icons.Material.Filled.FormatListBulleted,
_ => Icons.Material.Filled.DataArray,
};
private string FormatFileTimestamp(DateTime timestamp) => timestamp.ToString("g", this.fileInfoCulture);
private string FormatFileSize(long bytes)
{
if (bytes < BYTES_PER_KILOBYTE)
return string.Format(this.fileInfoCulture, 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);
var megabyte = kilobyte / BYTES_PER_KILOBYTE;
if (megabyte < BYTES_PER_KILOBYTE)
return string.Format(this.fileInfoCulture, TB("{0:0.##} MB"), megabyte);
var gigabyte = megabyte / BYTES_PER_KILOBYTE;
return string.Format(this.fileInfoCulture, 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;
}
}
}

View File

@ -1,6 +1,7 @@
using AIStudio.Components;
using AIStudio.Agents.AssistantAudit;
using AIStudio.Dialogs;
using AIStudio.Settings.DataModel;
using AIStudio.Tools.PluginSystem.Assistants;
using AIStudio.Tools.PluginSystem;
@ -14,6 +15,8 @@ public partial class Plugins : MSGComponentBase
private const string GROUP_ENABLED = "Enabled";
private const string GROUP_DISABLED = "Disabled";
private const string GROUP_INTERNAL = "Internal";
private DataAssistantPluginAudit AssistantPluginAuditSettings => this.SettingsManager.ConfigurationData.AssistantPluginAudit;
private TableGroupDefinition<IPluginMetadata> groupConfig = null!;
@ -56,7 +59,7 @@ public partial class Plugins : MSGComponentBase
return;
}
if (pluginMeta.Type is not PluginType.ASSISTANT || !this.SettingsManager.ConfigurationData.AssistantPluginAudit.RequireAuditBeforeActivation)
if (pluginMeta.Type is not PluginType.ASSISTANT || !this.AssistantPluginAuditSettings.RequireAuditBeforeActivation)
{
this.SettingsManager.ConfigurationData.EnabledPlugins.Add(pluginMeta.Id);
await this.SettingsManager.StoreSettings();
@ -72,12 +75,18 @@ public partial class Plugins : MSGComponentBase
var cachedAudit = this.SettingsManager.ConfigurationData.AssistantPluginAudits.FirstOrDefault(x => x.PluginId == pluginMeta.Id);
if (cachedAudit is not null && cachedAudit.PluginHash == pluginHash)
{
if (cachedAudit.Level < this.SettingsManager.ConfigurationData.AssistantPluginAudit.MinimumLevel && this.SettingsManager.ConfigurationData.AssistantPluginAudit.BlockActivationBelowMinimum)
if (cachedAudit.Level < this.AssistantPluginAuditSettings.MinimumLevel && this.AssistantPluginAuditSettings.BlockActivationBelowMinimum)
{
await this.DialogService.ShowMessageBox(this.T("Assistant Audit"), $"{cachedAudit.Level.GetName()}: {cachedAudit.Summary}", this.T("Close"));
return;
}
if (cachedAudit.Level < this.AssistantPluginAuditSettings.MinimumLevel &&
!await this.ConfirmActivationBelowMinimumAsync(pluginMeta.Name, cachedAudit.Level))
{
return;
}
this.SettingsManager.ConfigurationData.EnabledPlugins.Add(pluginMeta.Id);
await this.SettingsManager.StoreSettings();
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
@ -102,6 +111,26 @@ public partial class Plugins : MSGComponentBase
await this.SettingsManager.StoreSettings();
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
}
private async Task<bool> ConfirmActivationBelowMinimumAsync(string pluginName, AssistantAuditLevel actualLevel)
{
var dialogParameters = new DialogParameters<ConfirmDialog>
{
{
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<ConfirmDialog>(this.T("Potentially Dangerous Plugin"), dialogParameters,
DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result;
return dialogResult is not null && !dialogResult.Canceled;
}
private static bool IsSendingMail(string sourceUrl) => sourceUrl.TrimStart().StartsWith("mailto:", StringComparison.OrdinalIgnoreCase);

View File

@ -48,11 +48,11 @@ LANG_NAME = "Deutsch (Deutschland)"
UI_TEXT_CONTENT = {}
-- The Security Audit was unsuccessful, because the LLMs response was unusable. The Audit Level remains Unknown, so please try again later.
UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T2113359519"] = "Das Sicherheits-Audit war nicht erfolgreich, da die Antwort des LLM unbrauchbar war. Das Audit Level bleibt 'Unbekannt'. Bitte versuchen Sie es später erneut."
-- The security check could not be completed because the LLM's response was unusable. The audit level remains Unknown, so please try again later.
UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T2451573087"] = "Die Sicherheitsprüfung konnte nicht abgeschlossen werden, da die Antwort des LLM unbrauchbar war. Die Audit-Stufe bleibt „Unbekannt“, bitte versuchen Sie es später erneut."
-- No provider is configured for Security Audit-Agent.
UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T4000913009"] = "Für den Security Audit-Agenten ist kein Provider konfiguriert."
-- No provider is configured for the Security Audit Agent.
UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T3605554201"] = "Für den Sicherheitsprüfungs-Agenten ist kein Anbieter konfiguriert."
-- Needs Review
UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITLEVELEXTENSIONS::T1114911302"] = "Audit Erforderlich"
@ -2937,17 +2937,74 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T474393241"] = "Bitte wählen
-- Delete Workspace
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T701874671"] = "Arbeitsbereich löschen"
-- Entries: {0}
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1098127509"] = "Einträge: {0}"
-- {0:0.##} GB
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1224874808"] = "{0:0.##} GB"
-- Potentially Dangerous Plugin
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1229643769"] = "Potenziell gefährliches Plugin"
-- Plugin root
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1303883002"] = "Stammverzeichnis des Plugins"
-- Last modified
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1310524248"] = "Zuletzt geändert"
-- Count: {0}
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T131135808"] = "Anzahl: {0}"
-- {0:0.##} MB
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1357418474"] = "{0:0.##} MB"
-- No security issues were found during this check.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1423034104"] = "Bei dieser Überprüfung wurden keine Sicherheitsprobleme gefunden."
-- No provider configured
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1476185409"] = "Kein Provider konfiguriert"
-- {0:0.##} KB
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T14914764"] = "{0:0.##} KB"
-- Prompt: empty
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1533307170"] = "Prompt: leer"
-- This plugin is below the required safety level. Your settings still allow activation, but enabling it requires an extra confirmation because it may be unsafe.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1539381299"] = "Dieses Plugin unterschreitet das erforderliche Sicherheitsniveau. Ihre Einstellungen erlauben die Aktivierung zwar weiterhin, aber das Einschalten erfordert eine zusätzliche Bestätigung, da es möglicherweise unsicher ist."
-- Components
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1550582665"] = "Komponenten"
-- Created
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T165548891"] = "Erstellt"
-- Lua Manifest
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T165738710"] = "Lua-Manifest"
-- Required minimum level
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1862086522"] = "Erforderliches Mindest-Audit-Level"
-- Enable Assistant Plugin
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1676241565"] = "Assistant-Plugin aktivieren"
-- Unknown plugin
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1834795216"] = "Unbekanntes Plugin"
-- This plugin cannot be activated because its audit result is below the required safety level and your settings block activation in this case.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1839656215"] = "Dieses Plugin kann nicht aktiviert werden, weil sein Prüfergebnis unter dem erforderlichen Sicherheitsniveau liegt und Ihre Einstellungen die Aktivierung in diesem Fall blockieren."
-- Children: {0}
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T193192210"] = "Untergeordnete: {0}"
-- null
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1996966820"] = "null"
-- Properties
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2177370620"] = "Eigenschaften"
-- Items: {0}
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2204150657"] = "Elemente: {0}"
-- {0} B
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2562655035"] = "{0} B"
-- The assistant plugin could not be resolved for auditing.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T273798258"] = "Das Assistenten-Plugin konnte für die Überprüfung nicht aufgelöst werden."
@ -2955,17 +3012,50 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T273798258"] = "
-- Audit provider
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2757790517"] = "Provider prüfen"
-- Enable Plugin
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3233590741"] = "Plugin aktivieren"
-- Size
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2789707388"] = "Größe"
-- Prompt: set
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3156437951"] = "Prompt: festlegen"
-- Findings
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3224848879"] = "Ergebnisse"
-- {0} | Last modified {1}
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3350447013"] = "{0} | Zuletzt geändert {1}"
-- The assistant plugin \"{0}\" was audited with the level \"{1}\", which is below the required safety level \"{2}\". Your current settings still allow activation, but this may be unsafe. Do you really want to enable this plugin?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3418077666"] = "Das Assistenten-Plugin „{0}“ wurde mit der Stufe „{1}“ geprüft, die unter der erforderlichen Sicherheitsstufe „{2}“ liegt. Ihre aktuellen Einstellungen erlauben die Aktivierung dennoch, aber dies kann unsicher sein. Möchten Sie dieses Plugin wirklich aktivieren?"
-- Unknown
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3424652889"] = "Unbekannt"
-- Close
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3448155331"] = "Schließen"
-- The audit uses a simulated prompt preview. Empty or placeholder values in the preview are expected during this security check.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T439841458"] = "Das Audit verwendet eine simulierte Prompt-Vorschau. Leere oder Platzhalterwerte in der Vorschau sind während dieser Sicherheitsprüfung zu erwarten."
-- Value
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3511155050"] = "Wert"
-- Run Audit
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T564725977"] = "Prüfung ausführen"
-- Last accessed
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3579946376"] = "Zuletzt aufgerufen"
-- Unknown key
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3647690370"] = "Unbekannter Schlüssel"
-- Minimum required safety level
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3652671056"] = "Mindest erforderliches Sicherheitsniveau"
-- Unavailable
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3662391977"] = "Nicht verfügbar"
-- Plugin Structure
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T371537943"] = "Plugin-Struktur"
-- Audit Result
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3844960449"] = "Prüfungsergebnis"
-- empty
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T413646574"] = "leer"
-- Prompt Preview
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T576347259"] = "Prompt-Vorschau"
@ -2973,6 +3063,15 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T576347259"] = "
-- System Prompt
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T628396066"] = "System-Prompt"
-- This security check uses a sample prompt preview. Empty or placeholder values in the preview are expected.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T737998363"] = "Diese Sicherheitsprüfung verwendet eine Beispielvorschau des Prompts. Leere oder Platzhalterwerte in der Vorschau sind zu erwarten."
-- Safe
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T760494712"] = "Sicher"
-- Start Security Check
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T811648299"] = "Sicherheitsprüfung starten"
-- Cancel
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T900713019"] = "Abbrechen"
@ -5775,6 +5874,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T870640199"] = "Für einige Daten
-- Install Pandoc
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T986578435"] = "Pandoc installieren"
-- Potentially Dangerous Plugin
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1229643769"] = "Potenziell gefährliches Plugin"
-- Disable plugin
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1430375822"] = "Plugin deaktivieren"
@ -5796,6 +5898,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T2057806005"] = "Plugin aktivieren"
-- Plugins
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T2222816203"] = "Plugins"
-- 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?
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T2531356312"] = "Das Assistenten-Plugin „{0}“ wurde mit der Stufe „{1}“ geprüft, die unter der erforderlichen Mindeststufe „{2}“ liegt. Ihre aktuellen Einstellungen erlauben die Aktivierung trotzdem, aber das kann potenziell gefährlich sein. Möchten Sie dieses Plugin wirklich aktivieren?"
-- Enabled Plugins
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T2738444034"] = "Aktivierte Plugins"
@ -6531,6 +6636,81 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T3290596792"] = "Fehler beim Exp
-- Microsoft Word export successful
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T4256043333"] = "Export nach Microsoft Word erfolgreich"
-- Stack
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T135058847"] = "Stapel"
-- Button group
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T1392576058"] = "Schaltflächengruppe"
-- Profiles Selection
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T1437749589"] = "Profilauswahl"
-- Image
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T1494001562"] = "Bild"
-- Text Area
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T1593629311"] = "Textfeld"
-- File Content
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T165091006"] = "Dateiinhalt"
-- Grid Item
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T1991378436"] = "Rasterelement"
-- List
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T2368288673"] = "Liste"
-- Provider Selection
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T268262394"] = "Anbieterauswahl"
-- Root
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T2703841893"] = "Wurzel"
-- Container
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T2990360344"] = "Container"
-- Accordion
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T3372988345"] = "Akkordeon"
-- Date Range
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T3400615792"] = "Datumsbereich"
-- Plane Text
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T3557717784"] = "Unformatierter Text"
-- Switch
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T3656636817"] = "Umschalten"
-- Time
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T3756319748"] = "Zeit"
-- Dropdown
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T3829804792"] = "Dropdown"
-- Color
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T3853794552"] = "Farbe"
-- Accordion Section
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T4180733902"] = "Akkordeon-Abschnitt"
-- Heading
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T4231005109"] = "Überschrift"
-- Unknown Element
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T434854509"] = "Unbekanntes Element"
-- Web Content
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T590188228"] = "Web-Inhalte"
-- Grid
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T800286385"] = "Raster"
-- Button
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T864557713"] = "Schaltfläche"
-- Date
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T995259257"] = "Datum"
-- Failed to parse the UI render tree from the ASSISTANT lua table.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T1318499252"] = "Der UI-Render-Baum konnte nicht aus der ASSISTANT-Lua-Tabelle geparst werden."

View File

@ -48,11 +48,11 @@ LANG_NAME = "English (United States)"
UI_TEXT_CONTENT = {}
-- The Security Audit was unsuccessful, because the LLMs response was unusable. The Audit Level remains Unknown, so please try again later.
UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T2113359519"] = "The Security Audit was unsuccessful, because the LLMs response was unusable. The Audit Level remains Unknown, so please try again later."
-- The security check could not be completed because the LLM's response was unusable. The audit level remains Unknown, so please try again later.
UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T2451573087"] = "The security check could not be completed because the LLM's response was unusable. The audit level remains Unknown, so please try again later."
-- No provider is configured for Security Audit-Agent.
UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T4000913009"] = "No provider is configured for Security Audit-Agent."
-- No provider is configured for the Security Audit Agent.
UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T3605554201"] = "No provider is configured for the Security Audit Agent."
-- Needs Review
UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITLEVELEXTENSIONS::T1114911302"] = "Needs Review"
@ -2937,17 +2937,74 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T474393241"] = "Please select
-- Delete Workspace
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T701874671"] = "Delete Workspace"
-- Entries: {0}
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1098127509"] = "Entries: {0}"
-- {0:0.##} GB
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1224874808"] = "{0:0.##} GB"
-- Potentially Dangerous Plugin
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1229643769"] = "Potentially Dangerous Plugin"
-- Plugin root
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1303883002"] = "Plugin root"
-- Last modified
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1310524248"] = "Last modified"
-- Count: {0}
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T131135808"] = "Count: {0}"
-- {0:0.##} MB
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1357418474"] = "{0:0.##} MB"
-- No security issues were found during this check.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1423034104"] = "No security issues were found during this check."
-- No provider configured
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1476185409"] = "No provider configured"
-- {0:0.##} KB
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T14914764"] = "{0:0.##} KB"
-- Prompt: empty
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1533307170"] = "Prompt: empty"
-- This plugin is below the required safety level. Your settings still allow activation, but enabling it requires an extra confirmation because it may be unsafe.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1539381299"] = "This plugin is below the required safety level. Your settings still allow activation, but enabling it requires an extra confirmation because it may be unsafe."
-- Components
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1550582665"] = "Components"
-- Created
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T165548891"] = "Created"
-- Lua Manifest
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T165738710"] = "Lua Manifest"
-- Required minimum level
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1862086522"] = "Required minimum level"
-- Enable Assistant Plugin
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1676241565"] = "Enable Assistant Plugin"
-- Unknown plugin
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1834795216"] = "Unknown plugin"
-- This plugin cannot be activated because its audit result is below the required safety level and your settings block activation in this case.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1839656215"] = "This plugin cannot be activated because its audit result is below the required safety level and your settings block activation in this case."
-- Children: {0}
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T193192210"] = "Children: {0}"
-- null
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1996966820"] = "null"
-- Properties
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2177370620"] = "Properties"
-- Items: {0}
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2204150657"] = "Items: {0}"
-- {0} B
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2562655035"] = "{0} B"
-- The assistant plugin could not be resolved for auditing.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T273798258"] = "The assistant plugin could not be resolved for auditing."
@ -2955,17 +3012,50 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T273798258"] = "
-- Audit provider
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2757790517"] = "Audit provider"
-- Enable Plugin
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3233590741"] = "Enable Plugin"
-- Size
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2789707388"] = "Size"
-- Prompt: set
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3156437951"] = "Prompt: set"
-- Findings
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3224848879"] = "Findings"
-- {0} | Last modified {1}
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3350447013"] = "{0} | Last modified {1}"
-- The assistant plugin \"{0}\" was audited with the level \"{1}\", which is below the required safety level \"{2}\". Your current settings still allow activation, but this may be unsafe. Do you really want to enable this plugin?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3418077666"] = "The assistant plugin \\\"{0}\\\" was audited with the level \\\"{1}\\\", which is below the required safety level \\\"{2}\\\". Your current settings still allow activation, but this may be unsafe. Do you really want to enable this plugin?"
-- Unknown
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3424652889"] = "Unknown"
-- Close
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3448155331"] = "Close"
-- The audit uses a simulated prompt preview. Empty or placeholder values in the preview are expected during this security check.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T439841458"] = "The audit uses a simulated prompt preview. Empty or placeholder values in the preview are expected during this security check."
-- Value
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3511155050"] = "Value"
-- Run Audit
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T564725977"] = "Run Audit"
-- Last accessed
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3579946376"] = "Last accessed"
-- Unknown key
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3647690370"] = "Unknown key"
-- Minimum required safety level
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3652671056"] = "Minimum required safety level"
-- Unavailable
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3662391977"] = "Unavailable"
-- Plugin Structure
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T371537943"] = "Plugin Structure"
-- Audit Result
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3844960449"] = "Audit Result"
-- empty
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T413646574"] = "empty"
-- Prompt Preview
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T576347259"] = "Prompt Preview"
@ -2973,6 +3063,15 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T576347259"] = "
-- System Prompt
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T628396066"] = "System Prompt"
-- This security check uses a sample prompt preview. Empty or placeholder values in the preview are expected.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T737998363"] = "This security check uses a sample prompt preview. Empty or placeholder values in the preview are expected."
-- Safe
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T760494712"] = "Safe"
-- Start Security Check
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T811648299"] = "Start Security Check"
-- Cancel
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T900713019"] = "Cancel"
@ -5775,6 +5874,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T870640199"] = "For some data tra
-- Install Pandoc
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T986578435"] = "Install Pandoc"
-- Potentially Dangerous Plugin
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1229643769"] = "Potentially Dangerous Plugin"
-- Disable plugin
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1430375822"] = "Disable plugin"
@ -5796,6 +5898,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T2057806005"] = "Enable plugin"
-- Plugins
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T2222816203"] = "Plugins"
-- 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?
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T2531356312"] = "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?"
-- Enabled Plugins
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T2738444034"] = "Enabled Plugins"
@ -6531,6 +6636,81 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T3290596792"] = "Error during Mi
-- Microsoft Word export successful
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOCEXPORT::T4256043333"] = "Microsoft Word export successful"
-- Stack
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T135058847"] = "Stack"
-- Button group
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T1392576058"] = "Button group"
-- Profiles Selection
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T1437749589"] = "Profiles Selection"
-- Image
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T1494001562"] = "Image"
-- Text Area
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T1593629311"] = "Text Area"
-- File Content
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T165091006"] = "File Content"
-- Grid Item
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T1991378436"] = "Grid Item"
-- List
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T2368288673"] = "List"
-- Provider Selection
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T268262394"] = "Provider Selection"
-- Root
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T2703841893"] = "Root"
-- Container
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T2990360344"] = "Container"
-- Accordion
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T3372988345"] = "Accordion"
-- Date Range
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T3400615792"] = "Date Range"
-- Plane Text
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T3557717784"] = "Plane Text"
-- Switch
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T3656636817"] = "Switch"
-- Time
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T3756319748"] = "Time"
-- Dropdown
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T3829804792"] = "Dropdown"
-- Color
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T3853794552"] = "Color"
-- Accordion Section
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T4180733902"] = "Accordion Section"
-- Heading
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T4231005109"] = "Heading"
-- Unknown Element
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T434854509"] = "Unknown Element"
-- Web Content
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T590188228"] = "Web Content"
-- Grid
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T800286385"] = "Grid"
-- Button
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T864557713"] = "Button"
-- Date
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::DATAMODEL::ASSISTANTCOMPONENTTYPE::T995259257"] = "Date"
-- Failed to parse the UI render tree from the ASSISTANT lua table.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T1318499252"] = "Failed to parse the UI render tree from the ASSISTANT lua table."

View File

@ -1,4 +1,6 @@
namespace AIStudio.Tools.PluginSystem.Assistants.DataModel;
using System.ComponentModel.DataAnnotations;
namespace AIStudio.Tools.PluginSystem.Assistants.DataModel;
public enum AssistantComponentType
{
@ -27,3 +29,66 @@ public enum AssistantComponentType
LAYOUT_ACCORDION,
LAYOUT_ACCORDION_SECTION,
}
public static class AssistantComponentTypeExtensions
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(AssistantComponentTypeExtensions).Namespace, nameof(AssistantComponentTypeExtensions));
public static string GetDisplayName(this AssistantComponentType type) => type switch
{
AssistantComponentType.FORM => TB("Root"),
AssistantComponentType.TEXT_AREA => TB("Text Area"),
AssistantComponentType.BUTTON => TB("Button"),
AssistantComponentType.BUTTON_GROUP => TB("Button group"),
AssistantComponentType.DROPDOWN => TB("Dropdown"),
AssistantComponentType.PROVIDER_SELECTION => TB("Provider Selection"),
AssistantComponentType.PROFILE_SELECTION => TB("Profiles Selection"),
AssistantComponentType.SWITCH => TB("Switch"),
AssistantComponentType.HEADING => TB("Heading"),
AssistantComponentType.TEXT => TB("Plane Text"),
AssistantComponentType.LIST => TB("List"),
AssistantComponentType.WEB_CONTENT_READER => TB("Web Content"),
AssistantComponentType.FILE_CONTENT_READER => TB("File Content"),
AssistantComponentType.IMAGE => TB("Image"),
AssistantComponentType.COLOR_PICKER => TB("Color"),
AssistantComponentType.DATE_PICKER => TB("Date"),
AssistantComponentType.DATE_RANGE_PICKER => TB("Date Range"),
AssistantComponentType.TIME_PICKER => TB("Time"),
AssistantComponentType.LAYOUT_ITEM => TB("Grid Item"),
AssistantComponentType.LAYOUT_GRID => TB("Grid"),
AssistantComponentType.LAYOUT_PAPER => TB("Container"),
AssistantComponentType.LAYOUT_STACK => TB("Stack"),
AssistantComponentType.LAYOUT_ACCORDION => TB("Accordion"),
AssistantComponentType.LAYOUT_ACCORDION_SECTION => TB("Accordion Section"),
_ => TB("Unknown Element")
};
public static string GetIcon(this AssistantComponentType type) => type switch
{
AssistantComponentType.BUTTON => MudBlazor.Icons.Material.Filled.AdsClick,
AssistantComponentType.BUTTON_GROUP => MudBlazor.Icons.Material.Filled.LinearScale,
AssistantComponentType.DROPDOWN => MudBlazor.Icons.Material.Filled.Rule,
AssistantComponentType.PROVIDER_SELECTION => MudBlazor.Icons.Material.Filled.Memory,
AssistantComponentType.PROFILE_SELECTION => MudBlazor.Icons.Material.Filled.Badge,
AssistantComponentType.SWITCH => MudBlazor.Icons.Material.Filled.ToggleOn,
AssistantComponentType.HEADING => MudBlazor.Icons.Material.Filled.Title,
AssistantComponentType.TEXT => MudBlazor.Icons.Material.Filled.TextFields,
AssistantComponentType.TEXT_AREA => MudBlazor.Icons.Material.Filled.Wysiwyg,
AssistantComponentType.LIST => MudBlazor.Icons.Material.Filled.List,
AssistantComponentType.WEB_CONTENT_READER => MudBlazor.Icons.Material.Filled.Public,
AssistantComponentType.FILE_CONTENT_READER => MudBlazor.Icons.Material.Filled.AttachFile,
AssistantComponentType.IMAGE => MudBlazor.Icons.Material.Filled.Image,
AssistantComponentType.COLOR_PICKER => MudBlazor.Icons.Material.Filled.Palette,
AssistantComponentType.DATE_PICKER => MudBlazor.Icons.Material.Filled.CalendarMonth,
AssistantComponentType.DATE_RANGE_PICKER => MudBlazor.Icons.Material.Filled.DateRange,
AssistantComponentType.TIME_PICKER => MudBlazor.Icons.Material.Filled.Schedule,
AssistantComponentType.LAYOUT_ITEM => MudBlazor.Icons.Material.Filled.DashboardCustomize,
AssistantComponentType.LAYOUT_GRID => MudBlazor.Icons.Material.Filled.GridView,
AssistantComponentType.LAYOUT_PAPER => MudBlazor.Icons.Material.Filled.Inbox,
AssistantComponentType.LAYOUT_STACK => MudBlazor.Icons.Material.Filled.Layers,
AssistantComponentType.LAYOUT_ACCORDION => MudBlazor.Icons.Material.Filled.CalendarViewDay,
AssistantComponentType.LAYOUT_ACCORDION_SECTION => MudBlazor.Icons.Material.Filled.HorizontalSplit,
AssistantComponentType.FORM => MudBlazor.Icons.Material.Filled.AccountTree,
_ => MudBlazor.Icons.Material.Filled.AccountTree,
};
}

View File

@ -1,3 +1,4 @@
using System.Collections.Immutable;
using AIStudio.Tools.PluginSystem.Assistants.DataModel;
using AIStudio.Tools.PluginSystem.Assistants.DataModel.Layout;
using Lua;
@ -29,6 +30,7 @@ public sealed class PluginAssistants(bool isInternal, LuaState state, PluginType
public AssistantForm? RootComponent { get; private set; }
public string AssistantTitle { get; private set; } = string.Empty;
public string AssistantDescription { get; private set; } = string.Empty;
public string RawSystemPrompt { get; private set; } = string.Empty;
public string SystemPrompt { get; private set; } = string.Empty;
public string SubmitText { get; private set; } = string.Empty;
public bool AllowProfiles { get; private set; } = true;
@ -112,9 +114,12 @@ public sealed class PluginAssistants(bool isInternal, LuaState state, PluginType
message = TB("ASSISTANT.BuildPrompt exists but is not a Lua function or has invalid syntax.");
}
var rawSystemPrompt = assistantSystemPrompt.Trim();
this.AssistantTitle = assistantTitle;
this.AssistantDescription = assistantDescription;
this.SystemPrompt = BuildSecureSystemPrompt(assistantSystemPrompt);
this.RawSystemPrompt = rawSystemPrompt;
this.SystemPrompt = BuildSecureSystemPrompt(rawSystemPrompt);
this.SubmitText = assistantSubmitText;
this.AllowProfiles = assistantAllowProfiles;
@ -189,19 +194,52 @@ public sealed class PluginAssistants(bool isInternal, LuaState state, PluginType
return builder.ToString().TrimEnd();
}
public string ReadManifestCode()
public ImmutableDictionary<string, string> ReadAllLuaFiles()
{
var manifestPath = Path.Combine(this.PluginPath, "plugin.lua");
return File.Exists(manifestPath) ? File.ReadAllText(manifestPath) : string.Empty;
if (!Directory.Exists(this.PluginPath))
return ImmutableDictionary.Create<string, string>();
var fileMap = ImmutableDictionary.CreateBuilder<string, string>(StringComparer.Ordinal);
foreach (var filePath in Directory.EnumerateFiles(this.PluginPath, "*.lua", SearchOption.AllDirectories).OrderBy(path => path, StringComparer.Ordinal))
{
var relativePath = Path.GetRelativePath(this.PluginPath, filePath);
fileMap[relativePath] = File.ReadAllText(filePath);
}
return fileMap.ToImmutable();
}
/// <summary>
/// Computes a stable audit hash across all Lua files by hashing a canonical
/// sequence of relative path length, relative path, content length, and content
/// for each file in ordinal path order.
/// </summary>
public string ComputeAuditHash()
{
var manifestCode = this.ReadManifestCode();
if (string.IsNullOrWhiteSpace(manifestCode))
var luaFiles = this.ReadAllLuaFiles();
if (luaFiles.Count == 0)
return string.Empty;
var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(manifestCode));
using var stream = new MemoryStream();
using var writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true);
foreach (var (relativePath, content) in luaFiles.OrderBy(pair => pair.Key, StringComparer.Ordinal))
{
var normalizedPath = relativePath.Replace('\\', '/');
var pathBytes = Encoding.UTF8.GetBytes(normalizedPath);
var contentBytes = Encoding.UTF8.GetBytes(content);
writer.Write(pathBytes.Length);
writer.Write(pathBytes);
writer.Write(contentBytes.Length);
writer.Write(contentBytes);
}
writer.Flush();
var bytes = SHA256.HashData(stream.ToArray());
return Convert.ToHexString(bytes);
}