Fixed agents being left without a model when none was set aside for them (#985)
Some checks are pending
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Read metadata (push) Blocked by required conditions
Build and Release / Sync Flatpak repo (push) Blocked by required conditions
Build and Release / Collect Flatpak artifacts (push) Blocked by required conditions
Build and Release / Verify (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions

This commit is contained in:
Thorsten Sommer 2026-09-19 21:39:16 +02:00 committed by GitHub
parent 1379ff6aab
commit 459165f1be
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 540 additions and 91 deletions

View File

@ -124,12 +124,19 @@ public sealed class AssistantAuditAgent(ILogger<AssistantAuditAgent> logger, ILo
/// Resolves and stores the provider configuration used for assistant plugin audits. /// Resolves and stores the provider configuration used for assistant plugin audits.
/// </summary> /// </summary>
/// <param name="fallbackProvider">The provider to use when no provider is configured for the audit agent.</param> /// <param name="fallbackProvider">The provider to use when no provider is configured for the audit agent.</param>
/// <returns>The configured provider, or <see cref="AIStudio.Settings.Provider.NONE"/> when no audit provider is configured.</returns> /// <returns>The configured provider, or Provider.NONE when no audit provider is configured.</returns>
/// <remarks>
/// A fallback is a provider somebody picked for something else: the assistant they were building,
/// the revision they asked for, the check they are standing in front of. Whether it may read a
/// plugin's source and its Lua files is decided by what this agent requires, not by what it was
/// picked under, so it has to clear this agent's confidence bar before it is used. Otherwise a
/// provider an organization ruled out for audits would see the very thing it was ruled out for.
/// </remarks>
public AIStudio.Settings.Provider ResolveProvider(AIStudio.Settings.Provider? fallbackProvider = null) public AIStudio.Settings.Provider ResolveProvider(AIStudio.Settings.Provider? fallbackProvider = null)
{ {
var provider = this.SettingsManager.GetPreselectedProvider(Tools.Components.AGENT_ASSISTANT_PLUGIN_AUDIT, null, true); var provider = this.SettingsManager.GetPreselectedProvider(Tools.Components.AGENT_ASSISTANT_PLUGIN_AUDIT, null, true);
if (provider == AIStudio.Settings.Provider.NONE && fallbackProvider is not null) if (provider == AIStudio.Settings.Provider.NONE && fallbackProvider is { } candidate && this.SettingsManager.IsProviderConfident(candidate, Tools.Components.AGENT_ASSISTANT_PLUGIN_AUDIT))
provider = fallbackProvider; provider = candidate;
this.ProviderSettings = provider; this.ProviderSettings = provider;
return provider; return provider;
@ -149,12 +156,24 @@ public sealed class AssistantAuditAgent(ILogger<AssistantAuditAgent> logger, ILo
var provider = this.ResolveProvider(fallbackProvider); var provider = this.ResolveProvider(fallbackProvider);
if (provider == AIStudio.Settings.Provider.NONE) if (provider == AIStudio.Settings.Provider.NONE)
{ {
await MessageBus.INSTANCE.SendError(new (Icons.Material.Filled.SettingsSuggest, string.Format(TB("No provider is configured for the Security Audit Agent.")))); //
// There are two ways to end up here, and they send the user to different places: nobody
// named a provider, or the one at hand is not trusted enough for an audit. Saying that
// none is configured while one sits right there would send them looking in vain.
//
var wasFallbackRejected = fallbackProvider is { UsedLLMProvider: not LLMProviders.NONE };
var message = wasFallbackRejected
? TB("The selected provider is not trusted enough for security checks. Pick one which meets the confidence required here, or choose a dedicated provider for security checks in the app settings.")
: TB("No provider is configured for the Security Audit Agent.");
await MessageBus.INSTANCE.SendError(new (Icons.Material.Filled.SettingsSuggest, message));
return new AssistantAuditResult return new AssistantAuditResult
{ {
Level = nameof(AssistantAuditLevel.UNKNOWN), Level = nameof(AssistantAuditLevel.UNKNOWN),
Summary = TB("No audit provider is configured."), Summary = wasFallbackRejected
? TB("The provider is not trusted enough for security checks.")
: TB("No audit provider is configured."),
}; };
} }

View File

@ -52,12 +52,18 @@ UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T2034826
-- The security check could not be completed because the LLM's 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." 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."
-- The provider is not trusted enough for security checks.
UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T2861409367"] = "The provider is not trusted enough for security checks."
-- The audit agent did not return a usable response. -- The audit agent did not return a usable response.
UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T3310188890"] = "The audit agent did not return a usable response." UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T3310188890"] = "The audit agent did not return a usable response."
-- No provider is configured for the 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." UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T3605554201"] = "No provider is configured for the Security Audit Agent."
-- The selected provider is not trusted enough for security checks. Pick one which meets the confidence required here, or choose a dedicated provider for security checks in the app settings.
UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T4104574219"] = "The selected provider is not trusted enough for security checks. Pick one which meets the confidence required here, or choose a dedicated provider for security checks in the app settings."
-- The audit result was empty. -- The audit result was empty.
UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T432419958"] = "The audit result was empty." UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T432419958"] = "The audit result was empty."
@ -4291,15 +4297,18 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T3980535867"] = "Please
-- Attached file '{0}'. -- Attached file '{0}'.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T853724151"] = "Attached file '{0}'." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T853724151"] = "Attached file '{0}'."
-- The selected model does not meet the confidence requirements of the content cleaner. Please select another model, or configure an eligible one in the app settings.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T1028731446"] = "The selected model does not meet the confidence requirements of the content cleaner. Please select another model, or configure an eligible one in the app settings."
-- The content cleaner uses the model of this assistant. Please select one below.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T1160768613"] = "The content cleaner uses the model of this assistant. Please select one below."
-- The content is cleaned using an LLM agent: the main content is extracted, advertisements and other irrelevant things are attempted to be removed; relative links are attempted to be converted into absolute links so that they can be used. -- The content is cleaned using an LLM agent: the main content is extracted, advertisements and other irrelevant things are attempted to be removed; relative links are attempted to be converted into absolute links so that they can be used.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T1164201762"] = "The content is cleaned using an LLM agent: the main content is extracted, advertisements and other irrelevant things are attempted to be removed; relative links are attempted to be converted into absolute links so that they can be used." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T1164201762"] = "The content is cleaned using an LLM agent: the main content is extracted, advertisements and other irrelevant things are attempted to be removed; relative links are attempted to be converted into absolute links so that they can be used."
-- Fetch -- Fetch
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T1396322691"] = "Fetch" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T1396322691"] = "Fetch"
-- Please select a provider to use the cleanup agent.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T2035652317"] = "Please select a provider to use the cleanup agent."
-- Please provide a URL to load the content from. -- Please provide a URL to load the content from.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T2235427807"] = "Please provide a URL to load the content from." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T2235427807"] = "Please provide a URL to load the content from."
@ -4333,6 +4342,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T3825586228"] = "Please p
-- Show web content options -- Show web content options
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T4249712357"] = "Show web content options" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T4249712357"] = "Show web content options"
-- The content was loaded, but not cleaned: no model is available for the content cleaner.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T903778235"] = "The content was loaded, but not cleaned: no model is available for the content cleaner."
-- Loading -- Loading
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENTSTEPSEXTENSIONS::T1404011351"] = "Loading" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENTSTEPSEXTENSIONS::T1404011351"] = "Loading"
@ -5422,9 +5434,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1357418474"] =
-- No security issues were found during this check. -- No security issues were found during this check.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1423034104"] = "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 -- {0:0.##} KB
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T14914764"] = "{0:0.##} KB" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T14914764"] = "{0:0.##} KB"
@ -5464,6 +5473,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1996966820"] =
-- Properties -- Properties
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2177370620"] = "Properties" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2177370620"] = "Properties"
-- Model
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2189814010"] = "Model"
-- Items: {0} -- Items: {0}
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2204150657"] = "Items: {0}" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2204150657"] = "Items: {0}"
@ -5473,12 +5485,12 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2562655035"] =
-- The assistant plugin could not be resolved for auditing. -- 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." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T273798258"] = "The assistant plugin could not be resolved for auditing."
-- Audit provider
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2757790517"] = "Audit provider"
-- Size -- Size
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2789707388"] = "Size" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2789707388"] = "Size"
-- No model configured
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2941224401"] = "No model configured"
-- Prompt: set -- Prompt: set
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3156437951"] = "Prompt: set" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3156437951"] = "Prompt: set"
@ -5527,6 +5539,12 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T4229995215"] =
-- 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? -- 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::T521056824"] = "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::T521056824"] = "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?"
-- Audit model
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T532102309"] = "Audit model"
-- No model is set for security checks, neither for this agent nor for the app as a whole. Choose one here to check this plugin. Your choice applies to this check only; you can set a permanent one in the app settings.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T614645381"] = "No model is set for security checks, neither for this agent nor for the app as a whole. Choose one here to check this plugin. Your choice applies to this check only; you can set a permanent one in the app settings."
-- System Prompt -- System Prompt
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T628396066"] = "System Prompt" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T628396066"] = "System Prompt"
@ -5539,6 +5557,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T760494712"] = "
-- Start Security Check -- Start Security Check
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T811648299"] = "Start Security Check" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T811648299"] = "Start Security Check"
-- Please select a model.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T818893091"] = "Please select a model."
-- Cancel -- Cancel
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T900713019"] = "Cancel" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T900713019"] = "Cancel"

View File

@ -3,7 +3,7 @@
@{ @{
var availableProviderItems = this.GetAvailableProviderSelectionItems().ToList(); var availableProviderItems = this.GetAvailableProviderSelectionItems().ToList();
} }
<MudSelect T="Provider" Value="@this.ProviderSettings" ValueChanged="@this.SelectionChanged" Validation="@this.ValidateProvider" Margin="Margin.Dense" Label="@T("Provider")" Class="mb-3 rounded-lg" OuterClass="flex-grow-0" Variant="Variant.Outlined" Disabled="@this.Disabled"> <MudSelect T="Provider" Value="@this.ProviderSettings" ValueChanged="@this.SelectionChanged" Validation="@this.ValidateProvider" Margin="Margin.Dense" Label="@(this.Label ?? T("Provider"))" Class="mb-3 rounded-lg" OuterClass="flex-grow-0" Variant="Variant.Outlined" Disabled="@this.Disabled">
@foreach (var providerItem in availableProviderItems) @foreach (var providerItem in availableProviderItems)
{ {
<MudSelectItem Value="@providerItem.Provider"> <MudSelectItem Value="@providerItem.Provider">

View File

@ -19,6 +19,17 @@ public partial class ProviderSelection : MSGComponentBase
[Parameter] [Parameter]
public Func<AIStudio.Settings.Provider, string?> ValidateProvider { get; set; } = _ => null; public Func<AIStudio.Settings.Provider, string?> ValidateProvider { get; set; } = _ => null;
/// <summary>
/// What this place calls the thing being picked, when "Provider" is not the word it uses.
/// </summary>
/// <remarks>
/// Some places have the user pick a provider in order to set it up, and there the word is right.
/// Others have them pick one to get a job done, and speak of the model throughout. A field
/// labelled "Provider" in the middle of such a text reads like a second, different choice.
/// </remarks>
[Parameter]
public string? Label { get; set; }
/// <summary> /// <summary>
/// Gets or sets whether provider selection is disabled. /// Gets or sets whether provider selection is disabled.
/// </summary> /// </summary>

View File

@ -3,7 +3,13 @@
<MudTextSwitch Label="@T("Read content from web?")" Disabled="@this.AgentIsRunning" Value="@this.Preselect" ValueChanged="@this.ShowWebContentReaderChanged" LabelOn="@T("Show web content options")" LabelOff="@T("Hide web content options")" /> <MudTextSwitch Label="@T("Read content from web?")" Disabled="@this.AgentIsRunning" Value="@this.Preselect" ValueChanged="@this.ShowWebContentReaderChanged" LabelOn="@T("Show web content options")" LabelOff="@T("Hide web content options")" />
@if (this.Preselect) @if (this.Preselect)
{ {
<MudTextSwitch Label="@T("Cleanup content by using an LLM agent?")" Value="@this.PreselectContentCleanerAgent" ValueChanged="@this.UseContentCleanerAgentChanged" Validation="@this.ValidateProvider" Disabled="@this.AgentIsRunning" LabelOn="@T("The content is cleaned using an LLM agent: the main content is extracted, advertisements and other irrelevant things are attempted to be removed; relative links are attempted to be converted into absolute links so that they can be used.")" LabelOff="@T("No content cleaning")" /> <MudTextSwitch Label="@T("Cleanup content by using an LLM agent?")" Value="@this.PreselectContentCleanerAgent" ValueChanged="@this.UseContentCleanerAgentChanged" Disabled="@this.AgentIsRunning" LabelOn="@T("The content is cleaned using an LLM agent: the main content is extracted, advertisements and other irrelevant things are attempted to be removed; relative links are attempted to be converted into absolute links so that they can be used.")" LabelOff="@T("No content cleaning")" />
@if (this.ContentCleanerHint is { } contentCleanerHint)
{
<MudText Typo="Typo.body2" Color="Color.Error" Class="mb-3">
@contentCleanerHint
</MudText>
}
<MudStack Row="@true" AlignItems="@AlignItems.Baseline" Class="mb-3"> <MudStack Row="@true" AlignItems="@AlignItems.Baseline" Class="mb-3">
<MudTextField T="string" Label="@T("URL from which to load the content")" Value="@this.URL" ValueChanged="@this.URLValueChanged" Validation="@this.ValidateURL" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Link" Placeholder="https://..." HelperText="@T("Loads the content from your URL. Does not work when the content is hidden behind a paywall.")" Variant="Variant.Outlined" Immediate="@true" Disabled="@this.AgentIsRunning"/> <MudTextField T="string" Label="@T("URL from which to load the content")" Value="@this.URL" ValueChanged="@this.URLValueChanged" Validation="@this.ValidateURL" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Link" Placeholder="https://..." HelperText="@T("Loads the content from your URL. Does not work when the content is hidden behind a paywall.")" Variant="Variant.Outlined" Immediate="@true" Disabled="@this.AgentIsRunning"/>
<MudButton Disabled="@(!this.IsReady || this.AgentIsRunning)" Variant="Variant.Filled" Size="Size.Large" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Download" OnClick="@this.LoadFromWeb"> <MudButton Disabled="@(!this.IsReady || this.AgentIsRunning)" Variant="Variant.Filled" Size="Size.Large" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Download" OnClick="@this.LoadFromWeb">

View File

@ -72,33 +72,66 @@ public partial class ReadWebContent : MSGComponentBase
private readonly Process<ReadWebContentSteps> process = Process<ReadWebContentSteps>.INSTANCE; private readonly Process<ReadWebContentSteps> process = Process<ReadWebContentSteps>.INSTANCE;
private ProcessStepValue processStep; private ProcessStepValue processStep;
private bool isProviderValid;
/// <summary>
/// The model the content cleaner runs with.
/// </summary>
/// <remarks>
/// This is a resolved value, not a chosen one: the reader has no model selection of its own,
/// it takes what the assistant around it uses, unless a dedicated one for the cleaner or an
/// app-wide default takes precedence. Because the assistant's model can change at any moment,
/// this is resolved again on every render instead of being remembered from the first one.
/// </remarks>
private AIStudio.Settings.Provider providerSettings = AIStudio.Settings.Provider.NONE; private AIStudio.Settings.Provider providerSettings = AIStudio.Settings.Provider.NONE;
#region Overrides of ComponentBase #region Overrides of ComponentBase
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
this.ProviderSettings = this.SettingsManager.GetPreselectedProvider(Tools.Components.AGENT_TEXT_CONTENT_CLEANER, this.ProviderSettings.Id, true); this.ApplyFilters([], [ Event.CONFIGURATION_CHANGED ]);
this.providerSettings = this.ProviderSettings; this.ResolveProvider();
this.ValidateProvider(this.PreselectContentCleanerAgent);
await base.OnInitializedAsync(); await base.OnInitializedAsync();
} }
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
if (!this.SettingsManager.ConfigurationData.TextContentCleaner.PreselectAgentOptions) this.ResolveProvider();
this.providerSettings = this.ProviderSettings;
this.ValidateProvider(this.PreselectContentCleanerAgent);
await base.OnParametersSetAsync(); await base.OnParametersSetAsync();
} }
#endregion #endregion
#region Overrides of MSGComponentBase
protected override Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
{
if (triggeredEvent is Event.CONFIGURATION_CHANGED)
{
//
// A dedicated model for the cleaner, or the app-wide default, may be set while this
// assistant is open. Nothing about that reaches us as a parameter, so without this the
// user would have to leave the assistant and come back for it to take effect.
//
this.ResolveProvider();
this.StateHasChanged();
}
return Task.CompletedTask;
}
#endregion
/// <summary>
/// Determines the model the content cleaner runs with.
/// </summary>
/// <remarks>
/// Called from both lifecycle methods, and with the same arguments: the assistant's model is a
/// parameter, and a parameter arrives whenever the parent renders. Resolving only once would
/// leave the cleaner with whatever was set the first time this component was built.
/// </remarks>
private void ResolveProvider() => this.providerSettings = this.SettingsManager.GetPreselectedProvider(Tools.Components.AGENT_TEXT_CONTENT_CLEANER, this.ProviderSettings.Id, true);
private async Task LoadFromWeb() private async Task LoadFromWeb()
{ {
if(!this.IsReady) if(!this.IsReady)
@ -128,6 +161,16 @@ public partial class ReadWebContent : MSGComponentBase
markdown = retrievedPage.ExtractedPage.Markdown; markdown = retrievedPage.ExtractedPage.Markdown;
markdown = await this.PromptInjectionGuardService.SanitizeAsync(markdown, PromptInjectionSource.WebContent(this.URL)); markdown = await this.PromptInjectionGuardService.SanitizeAsync(markdown, PromptInjectionSource.WebContent(this.URL));
if (this.PreselectContentCleanerAgent && this.providerSettings == AIStudio.Settings.Provider.NONE)
{
//
// Say that the cleaning did not happen. The user asked for it, the page arrives,
// and without a word they would take the raw markdown -- navigation, cookie banner
// and advertising included -- for the cleaned result.
//
await this.MessageBus.SendError(new(Icons.Material.Filled.SettingsSuggest, T("The content was loaded, but not cleaned: no model is available for the content cleaner.")));
}
if (this.PreselectContentCleanerAgent && this.providerSettings != AIStudio.Settings.Provider.NONE) if (this.PreselectContentCleanerAgent && this.providerSettings != AIStudio.Settings.Provider.NONE)
{ {
this.AgentTextContentCleaner.ProviderSettings = this.providerSettings; this.AgentTextContentCleaner.ProviderSettings = this.providerSettings;
@ -183,19 +226,16 @@ public partial class ReadWebContent : MSGComponentBase
await this.ContentChanged.InvokeAsync(this.Content); await this.ContentChanged.InvokeAsync(this.Content);
} }
private bool IsReady /// <summary>
{ /// Whether the content can be fetched.
get /// </summary>
{ /// <remarks>
if(!this.UrlIsValid) /// A missing model for the content cleaner is deliberately not part of this. Cleaning is an
return false; /// option of the fetch, not a condition for it: making it one would leave the user with a
/// switch they turned on, no way to get their page, and a dead button to explain it. The page
if(this.PreselectContentCleanerAgent && !this.isProviderValid) /// is fetched, and LoadFromWeb says that it arrived uncleaned.
return false; /// </remarks>
private bool IsReady => this.UrlIsValid;
return true;
}
}
/// <summary> /// <summary>
/// Whether the current URL can be loaded. /// Whether the current URL can be loaded.
@ -222,18 +262,30 @@ public partial class ReadWebContent : MSGComponentBase
await this.PreselectContentCleanerAgentChanged.InvokeAsync(state); await this.PreselectContentCleanerAgentChanged.InvokeAsync(state);
} }
private string? ValidateProvider(bool shouldUseAgent) /// <summary>
/// Says why the content cleaner has no model, or nothing when it has one.
/// </summary>
/// <remarks>
/// This is a hint, not a validation: the cleaner is an option of the reader, and an option
/// nobody can use yet must not keep the assistant around it from running. It is also stated
/// rather than remembered, so that choosing a model below makes it disappear at once.
/// The two causes lead to different places, which is why they are told apart: either no model
/// was chosen at all, or the chosen one is not trusted enough for this agent.
/// </remarks>
private string? ContentCleanerHint
{ {
if(shouldUseAgent && this.providerSettings == AIStudio.Settings.Provider.NONE) get
{ {
this.isProviderValid = false; if(!this.PreselectContentCleanerAgent || this.providerSettings != AIStudio.Settings.Provider.NONE)
return T("Please select a provider to use the cleanup agent."); return null;
}
this.isProviderValid = true; if(this.ProviderSettings == AIStudio.Settings.Provider.NONE)
return null; return T("The content cleaner uses the model of this assistant. Please select one below.");
return T("The selected model does not meet the confidence requirements of the content cleaner. Please select another model, or configure an eligible one in the app settings.");
}
} }
private string? ValidateURL(string url) private string? ValidateURL(string url)
{ {
if(string.IsNullOrWhiteSpace(url)) if(string.IsNullOrWhiteSpace(url))

View File

@ -29,13 +29,25 @@
<MudText Typo="Typo.h6">@this.plugin.Name</MudText> <MudText Typo="Typo.h6">@this.plugin.Name</MudText>
<MudText Typo="Typo.body2" Class="mb-2">@this.plugin.Description</MudText> <MudText Typo="Typo.body2" Class="mb-2">@this.plugin.Description</MudText>
<MudText Typo="Typo.body2"> <MudText Typo="Typo.body2">
@T("Audit provider"): <strong>@this.ProviderLabel</strong> @T("Audit model"): <strong>@this.ProviderLabel</strong>
</MudText> </MudText>
<MudText Typo="Typo.body2"> <MudText Typo="Typo.body2">
@T("Minimum required safety level"): <strong>@this.MinimumLevelLabel</strong> @T("Minimum required safety level"): <strong>@this.MinimumLevelLabel</strong>
</MudText> </MudText>
</MudPaper> </MudPaper>
@if (this.NeedsProviderSelection && !this.securityState.IsEnterpriseApproved)
{
<MudPaper Class="pa-3 border-dashed border rounded-lg">
<MudText Typo="Typo.body2" Class="mb-3">
@T("No model is set for security checks, neither for this agent nor for the app as a whole. Choose one here to check this plugin. Your choice applies to this check only; you can set a permanent one in the app settings.")
</MudText>
<CascadingValue Value="Components.AGENT_ASSISTANT_PLUGIN_AUDIT">
<ProviderSelection @bind-ProviderSettings="@this.auditProviderSelection" ValidateProvider="@this.ValidatingProvider" Label="@T("Model")" Disabled="@this.isAuditing" />
</CascadingValue>
</MudPaper>
}
<MudExpansionPanels MultiExpansion="true"> <MudExpansionPanels MultiExpansion="true">
<MudExpansionPanel Expanded="true"> <MudExpansionPanel Expanded="true">
<TitleContent> <TitleContent>

View File

@ -39,11 +39,32 @@ public partial class AssistantPluginAuditDialog : MSGComponentBase
private bool isAuditing; private bool isAuditing;
private PluginAssistantSecurityState securityState = new(); private PluginAssistantSecurityState securityState = new();
/// <summary>
/// The provider the user picks inside this dialog when nothing is configured for the audit agent.
/// </summary>
/// <remarks>
/// It lives and dies with this dialog and is never written to the settings: an audit is a one-off
/// job, and the choice made here says nothing about which model the next one should use.
/// </remarks>
private AIStudio.Settings.Provider auditProviderSelection = AIStudio.Settings.Provider.NONE;
private AIStudio.Settings.Provider CurrentProvider => this.SettingsManager.GetPreselectedProvider(Tools.Components.AGENT_ASSISTANT_PLUGIN_AUDIT, null, true); private AIStudio.Settings.Provider CurrentProvider => this.SettingsManager.GetPreselectedProvider(Tools.Components.AGENT_ASSISTANT_PLUGIN_AUDIT, null, true);
private string ProviderLabel => this.CurrentProvider == AIStudio.Settings.Provider.NONE /// <summary>
? this.T("No provider configured") /// The provider this audit runs with: the configured one, or what the user picked here instead.
: $"{this.CurrentProvider.InstanceName} ({this.CurrentProvider.UsedLLMProvider.ToName()})"; /// </summary>
private AIStudio.Settings.Provider EffectiveProvider => this.CurrentProvider == AIStudio.Settings.Provider.NONE
? this.auditProviderSelection
: this.CurrentProvider;
/// <summary>
/// Whether this dialog has to offer a provider, because neither the audit agent nor the app has one.
/// </summary>
private bool NeedsProviderSelection => this.CurrentProvider == AIStudio.Settings.Provider.NONE;
private string ProviderLabel => this.EffectiveProvider == AIStudio.Settings.Provider.NONE
? T("No model configured")
: $"{this.EffectiveProvider.InstanceName} ({this.EffectiveProvider.UsedLLMProvider.ToName()})";
private DataAssistantPluginAudit AuditSettings => this.SettingsManager.ConfigurationData.AssistantPluginAudit; private DataAssistantPluginAudit AuditSettings => this.SettingsManager.ConfigurationData.AssistantPluginAudit;
@ -51,17 +72,41 @@ public partial class AssistantPluginAuditDialog : MSGComponentBase
private string MinimumLevelLabel => this.MinimumLevel.GetName(); private string MinimumLevelLabel => this.MinimumLevel.GetName();
private bool CanRunAudit => this.plugin is not null && this.CurrentProvider != AIStudio.Settings.Provider.NONE && !this.isAuditing && !this.securityState.IsEnterpriseApproved; private bool CanRunAudit => this.plugin is not null && this.EffectiveProvider != AIStudio.Settings.Provider.NONE && !this.isAuditing && !this.securityState.IsEnterpriseApproved;
private bool IsAuditBelowMinimum => this.audit is not null && this.audit.Level < this.MinimumLevel; /// <summary>
/// The audit result this dialog acts on: the one it has, unless that one concluded nothing.
/// </summary>
/// <remarks>
/// UNKNOWN is not a low audit level, it is the absence of a result: the model was unreachable,
/// the key was wrong, no provider was trusted enough. Everything which decides something has to
/// read it as no audit at all -- whether the plugin may be activated, and what this dialog hands
/// back to be stored. Otherwise a check which failed would unlock a plugin nobody has checked,
/// and storing it would replace the last result which did say something, because audits are kept
/// one per plugin. This is the rule PluginAssistantSecurityResolver already applies to the stored
/// audits. What the dialog shows the user still reads the raw result: a failed run is precisely
/// what they need to see.
/// </remarks>
private PluginAssistantAudit? ConclusiveAudit => this.audit is { Level: not AssistantAuditLevel.UNKNOWN } ? this.audit : null;
private bool IsActivationBlockedBySettings => this.AuditSettings.RequireAuditBeforeActivation && (this.audit is null || this.IsAuditBelowMinimum && this.AuditSettings.BlockActivationBelowMinimum); private bool IsAuditBelowMinimum => this.ConclusiveAudit is not null && this.ConclusiveAudit.Level < this.MinimumLevel;
private bool RequiresActivationConfirmation => this.audit is not null && this.IsAuditBelowMinimum && !this.IsActivationBlockedBySettings; private bool IsActivationBlockedBySettings => this.AuditSettings.RequireAuditBeforeActivation && (this.ConclusiveAudit is null || this.IsAuditBelowMinimum && this.AuditSettings.BlockActivationBelowMinimum);
private bool RequiresActivationConfirmation => this.ConclusiveAudit is not null && this.IsAuditBelowMinimum && !this.IsActivationBlockedBySettings;
private bool CanEnablePlugin => this.plugin is not null && !this.isAuditing && !this.IsActivationBlockedBySettings; private bool CanEnablePlugin => this.plugin is not null && !this.isAuditing && !this.IsActivationBlockedBySettings;
private Color EnableButtonColor => this.RequiresActivationConfirmation ? Color.Warning : Color.Success; private Color EnableButtonColor => this.RequiresActivationConfirmation ? Color.Warning : Color.Success;
/// <summary>
/// Whether this dialog has produced an audit result, which is why it offers no second run.
/// </summary>
/// <remarks>
/// A run which concluded nothing must not set this. It would leave the user in front of a plugin
/// they cannot check and cannot enable, with closing and reopening the dialog as the only way on
/// -- and a failed run is the one case where trying again is exactly the right thing to do.
/// </remarks>
private bool justAudited; private bool justAudited;
private const ushort BYTES_PER_KILOBYTE = 1024; private const ushort BYTES_PER_KILOBYTE = 1024;
@ -97,26 +142,39 @@ public partial class AssistantPluginAuditDialog : MSGComponentBase
try try
{ {
this.audit = await this.AssistantPluginAuditService.RunAuditAsync(this.plugin); //
// The provider picked here is handed over as the fallback: the audit service uses it only
// when nothing is configured for the audit agent, so an organization-wide provider keeps
// its precedence.
//
this.audit = await this.AssistantPluginAuditService.RunAuditAsync(this.plugin, fallbackProvider: this.auditProviderSelection);
this.securityState = PluginAssistantSecurityResolver.Resolve(this.SettingsManager, this.plugin); this.securityState = PluginAssistantSecurityResolver.Resolve(this.SettingsManager, this.plugin);
} }
finally finally
{ {
this.isAuditing = false; this.isAuditing = false;
this.justAudited = true; this.justAudited = this.ConclusiveAudit is not null;
await this.InvokeAsync(this.StateHasChanged); await this.InvokeAsync(this.StateHasChanged);
} }
} }
private string? ValidatingProvider(AIStudio.Settings.Provider provider)
{
if (provider.UsedLLMProvider == LLMProviders.NONE)
return T("Please select a model.");
return null;
}
private void CloseWithoutActivation() private void CloseWithoutActivation()
{ {
if (this.audit is null) if (this.ConclusiveAudit is null)
{ {
this.MudDialog.Cancel(); this.MudDialog.Cancel();
return; return;
} }
this.MudDialog.Close(DialogResult.Ok(new AssistantPluginAuditDialogResult(this.audit, false))); this.MudDialog.Close(DialogResult.Ok(new AssistantPluginAuditDialogResult(this.ConclusiveAudit, false)));
} }
private async Task EnablePlugin() private async Task EnablePlugin()
@ -130,7 +188,7 @@ public partial class AssistantPluginAuditDialog : MSGComponentBase
if (this.RequiresActivationConfirmation && !await this.ConfirmActivationBelowMinimumAsync()) if (this.RequiresActivationConfirmation && !await this.ConfirmActivationBelowMinimumAsync())
return; return;
this.MudDialog.Close(DialogResult.Ok(new AssistantPluginAuditDialogResult(this.audit, true))); this.MudDialog.Close(DialogResult.Ok(new AssistantPluginAuditDialogResult(this.ConclusiveAudit, true)));
} }
private async Task<bool> ConfirmActivationBelowMinimumAsync() private async Task<bool> ConfirmActivationBelowMinimumAsync()

View File

@ -198,7 +198,12 @@ public partial class AssistantPluginRevisionDialog : MSGComponentBase
await this.InvokeAsync(this.StateHasChanged); await this.InvokeAsync(this.StateHasChanged);
try try
{ {
var audit = await this.AssistantPluginAuditService.RunAuditAsync(updatedPlugin); //
// The provider the user picked for the revision serves as the fallback: it is used only
// when nothing is configured for the audit agent, and only when it is trusted enough for
// an audit. Without it, a revised plugin could not be checked at all here.
//
var audit = await this.AssistantPluginAuditService.RunAuditAsync(updatedPlugin, fallbackProvider: this.providerSettings);
if (audit.Level is AssistantAuditLevel.UNKNOWN) if (audit.Level is AssistantAuditLevel.UNKNOWN)
return audit; return audit;

View File

@ -54,12 +54,18 @@ UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T2034826
-- The security check could not be completed because the LLM's 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"] = "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." 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."
-- The provider is not trusted enough for security checks.
UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T2861409367"] = "Der Anbieter ist für Sicherheitsprüfungen nicht vertrauenswürdig genug."
-- The audit agent did not return a usable response. -- The audit agent did not return a usable response.
UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T3310188890"] = "Der Audit-Agent hat keine verwendbare Antwort zurückgegeben." UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T3310188890"] = "Der Audit-Agent hat keine verwendbare Antwort zurückgegeben."
-- No provider is configured for the Security Audit Agent. -- 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." UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T3605554201"] = "Für den Sicherheitsprüfungs-Agenten ist kein Anbieter konfiguriert."
-- The selected provider is not trusted enough for security checks. Pick one which meets the confidence required here, or choose a dedicated provider for security checks in the app settings.
UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T4104574219"] = "Der ausgewählte Anbieter ist für Sicherheitsprüfungen nicht vertrauenswürdig genug. Wählen Sie einen Anbieter aus, der das hier erforderliche Vertrauensniveau erfüllt, oder legen Sie in den App-Einstellungen einen speziellen Anbieter für Audits fest."
-- The audit result was empty. -- The audit result was empty.
UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T432419958"] = "Das Prüfergebnis war leer." UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T432419958"] = "Das Prüfergebnis war leer."
@ -4030,7 +4036,7 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ISSUES::T3229841001"] = "Probleme"
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEDTOOLSWARNING::T1319635088"] = "Einige der für diesen Durchlauf ausgewählten Werkzeuge sind nicht vollständig eingerichtet und bleiben daher ungenutzt: \"{0}\". Bitte vervollständigen Sie deren Einstellungen." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEDTOOLSWARNING::T1319635088"] = "Einige der für diesen Durchlauf ausgewählten Werkzeuge sind nicht vollständig eingerichtet und bleiben daher ungenutzt: \"{0}\". Bitte vervollständigen Sie deren Einstellungen."
-- Not all tools selected for this run can be used with the chosen AI provider: {0}. Please choose a provider with a higher confidence level to use all of them. -- Not all tools selected for this run can be used with the chosen AI provider: {0}. Please choose a provider with a higher confidence level to use all of them.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEDTOOLSWARNING::T2430645786"] = "Nicht alle für diesen Durchlauf ausgewählten Werkzeuge können mit dem gewählten KI-Anbieter „{0}“ verwendet werden. Bitte wählen Sie einen Anbieter mit einer höheren Vertrauensstufe, um alle Werkzeuge zu nutzen." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEDTOOLSWARNING::T2430645786"] = "Nicht alle für diesen Durchlauf ausgewählten Werkzeuge können mit dem gewählten KI-Anbieter „{0}“ verwendet werden. Bitte wählen Sie einen Anbieter mit einem höheren Vertrauensniveau, um alle Werkzeuge zu nutzen."
-- Tools were selected for this run, but the chosen model cannot use tools. It runs without them. Please choose a model which supports tools. -- Tools were selected for this run, but the chosen model cannot use tools. It runs without them. Please choose a model which supports tools.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEDTOOLSWARNING::T3008114108"] = "Für diesen Durchlauf wurden Werkzeuge ausgewählt, aber das ausgewählte Modell kann keine Werkzeuge verwenden. Es wird ohne sie ausgeführt. Bitte wählen Sie ein Modell, das Werkzeuge unterstützt." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEDTOOLSWARNING::T3008114108"] = "Für diesen Durchlauf wurden Werkzeuge ausgewählt, aber das ausgewählte Modell kann keine Werkzeuge verwenden. Es wird ohne sie ausgeführt. Bitte wählen Sie ein Modell, das Werkzeuge unterstützt."
@ -4293,15 +4299,18 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T3980535867"] = "Bitte w
-- Attached file '{0}'. -- Attached file '{0}'.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T853724151"] = "Datei „{0}“ angehängt." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T853724151"] = "Datei „{0}“ angehängt."
-- The selected model does not meet the confidence requirements of the content cleaner. Please select another model, or configure an eligible one in the app settings.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T1028731446"] = "Das ausgewählte Modell erfüllt nicht die Vertrauensanforderungen des Agenten zur Inhaltsbereinigung. Bitte wählen Sie ein anderes Modell aus oder konfigurieren Sie ein geeignetes Modell in den App-Einstellungen."
-- The content cleaner uses the model of this assistant. Please select one below.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T1160768613"] = "Der Agent zur Inhaltsbereinigung verwendet das Modell dieses Assistenten. Bitte wählen Sie unten eines aus."
-- The content is cleaned using an LLM agent: the main content is extracted, advertisements and other irrelevant things are attempted to be removed; relative links are attempted to be converted into absolute links so that they can be used. -- The content is cleaned using an LLM agent: the main content is extracted, advertisements and other irrelevant things are attempted to be removed; relative links are attempted to be converted into absolute links so that they can be used.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T1164201762"] = "Der Inhalt wird mithilfe eines LLM-Agents bereinigt: Der Hauptinhalt wird extrahiert, Werbung und andere irrelevante Elemente werden nach Möglichkeit entfernt. Relative Links werden nach Möglichkeit in absolute Links umgewandelt, damit sie verwendet werden können." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T1164201762"] = "Der Inhalt wird mithilfe eines LLM-Agents bereinigt: Der Hauptinhalt wird extrahiert, Werbung und andere irrelevante Elemente werden nach Möglichkeit entfernt. Relative Links werden nach Möglichkeit in absolute Links umgewandelt, damit sie verwendet werden können."
-- Fetch -- Fetch
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T1396322691"] = "Abrufen" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T1396322691"] = "Abrufen"
-- Please select a provider to use the cleanup agent.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T2035652317"] = "Bitte wählen Sie einen Anbieter aus, um den Bereinigungsagenten zu verwenden."
-- Please provide a URL to load the content from. -- Please provide a URL to load the content from.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T2235427807"] = "Bitte geben Sie eine URL an, von der der Inhalt geladen werden soll." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T2235427807"] = "Bitte geben Sie eine URL an, von der der Inhalt geladen werden soll."
@ -4335,6 +4344,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T3825586228"] = "Bitte ge
-- Show web content options -- Show web content options
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T4249712357"] = "Optionen für Webinhalte anzeigen" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T4249712357"] = "Optionen für Webinhalte anzeigen"
-- The content was loaded, but not cleaned: no model is available for the content cleaner.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T903778235"] = "Der Inhalt wurde geladen, aber nicht bereinigt: Für die Inhaltsbereinigung ist kein Modell verfügbar."
-- Loading -- Loading
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENTSTEPSEXTENSIONS::T1404011351"] = "Laden" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENTSTEPSEXTENSIONS::T1404011351"] = "Laden"
@ -5424,9 +5436,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1357418474"] =
-- No security issues were found during this check. -- No security issues were found during this check.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1423034104"] = "Bei dieser Überprüfung wurden keine Sicherheitsprobleme gefunden." 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 Anbieter konfiguriert"
-- {0:0.##} KB -- {0:0.##} KB
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T14914764"] = "{0:0.##} KB" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T14914764"] = "{0:0.##} KB"
@ -5466,6 +5475,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1996966820"] =
-- Properties -- Properties
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2177370620"] = "Eigenschaften" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2177370620"] = "Eigenschaften"
-- Model
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2189814010"] = "Modell"
-- Items: {0} -- Items: {0}
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2204150657"] = "Elemente: {0}" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2204150657"] = "Elemente: {0}"
@ -5475,12 +5487,12 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2562655035"] =
-- The assistant plugin could not be resolved for auditing. -- 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." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T273798258"] = "Das Assistenten-Plugin konnte für die Überprüfung nicht aufgelöst werden."
-- Audit provider
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2757790517"] = "Anbieter prüfen"
-- Size -- Size
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2789707388"] = "Größe" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2789707388"] = "Größe"
-- No model configured
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2941224401"] = "Kein Modell konfiguriert"
-- Prompt: set -- Prompt: set
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3156437951"] = "Prompt: festlegen" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3156437951"] = "Prompt: festlegen"
@ -5506,7 +5518,7 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3579946376"] =
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3647690370"] = "Unbekannter Schlüssel" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3647690370"] = "Unbekannter Schlüssel"
-- Minimum required safety level -- Minimum required safety level
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3652671056"] = "Mindest erforderliches Sicherheitsniveau" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3652671056"] = "Mindestens erforderliches Sicherheitsniveau"
-- Unavailable -- Unavailable
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3662391977"] = "Nicht verfügbar" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3662391977"] = "Nicht verfügbar"
@ -5529,6 +5541,12 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T4229995215"] =
-- 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? -- 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::T521056824"] = "Das Assistenz-Plugin „{0}“ wurde mit der Stufe „{1}“ geprüft, die unter der erforderlichen Sicherheitsstufe „{2}“ liegt. Ihre aktuellen Einstellungen erlauben die Aktivierung weiterhin, dies kann jedoch unsicher sein. Möchten Sie dieses Plugin wirklich aktivieren?" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T521056824"] = "Das Assistenz-Plugin „{0}“ wurde mit der Stufe „{1}“ geprüft, die unter der erforderlichen Sicherheitsstufe „{2}“ liegt. Ihre aktuellen Einstellungen erlauben die Aktivierung weiterhin, dies kann jedoch unsicher sein. Möchten Sie dieses Plugin wirklich aktivieren?"
-- Audit model
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T532102309"] = "Audit-Modell"
-- No model is set for security checks, neither for this agent nor for the app as a whole. Choose one here to check this plugin. Your choice applies to this check only; you can set a permanent one in the app settings.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T614645381"] = "Für Sicherheitsprüfungen ist weder für diesen Agenten noch appweit ein Modell festgelegt. Wählen Sie hier eines aus, um dieses Plugin zu prüfen. Ihre Auswahl gilt nur für diese Prüfung; in den App-Einstellungen können Sie ein dauerhaftes Modell festlegen."
-- System Prompt -- System Prompt
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T628396066"] = "System-Prompt" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T628396066"] = "System-Prompt"
@ -5541,6 +5559,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T760494712"] = "
-- Start Security Check -- Start Security Check
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T811648299"] = "Sicherheitsprüfung starten" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T811648299"] = "Sicherheitsprüfung starten"
-- Please select a model.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T818893091"] = "Bitte wählen Sie ein Modell aus."
-- Cancel -- Cancel
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T900713019"] = "Abbrechen" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T900713019"] = "Abbrechen"
@ -7993,7 +8014,7 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGJOBPOSTINGS::T378839
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGJOBPOSTINGS::T3825475093"] = "Die Stellenbeschreibung vorauswählen?" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGJOBPOSTINGS::T3825475093"] = "Die Stellenbeschreibung vorauswählen?"
-- Content cleaner agent is preselected -- Content cleaner agent is preselected
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T1013787967"] = "Der Content Cleaner-Agent ist vorausgewählt" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T1013787967"] = "Agent zur Inhaltsbereinigung ist vorausgewählt"
-- Web content reader is shown -- Web content reader is shown
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T1030372436"] = "Web-Content-Reader wird angezeigt" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T1030372436"] = "Web-Content-Reader wird angezeigt"
@ -8032,7 +8053,7 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T2322771
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T252916114"] = "Rechtsprüfungsoptionen sind vorausgewählt" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T252916114"] = "Rechtsprüfungsoptionen sind vorausgewählt"
-- When enabled, the content cleaner agent is preselected. This is might be useful when you prefer to clean up the legal content before translating it. -- When enabled, the content cleaner agent is preselected. This is might be useful when you prefer to clean up the legal content before translating it.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T2746583995"] = "Wenn aktiviert, ist der Content Cleaner Agent vorausgewählt. Das kann nützlich sein, wenn Sie den rechtlichen Inhalt bereinigen möchten, bevor Sie ihn übersetzen." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T2746583995"] = "Wenn aktiviert, ist der Agent zur Inhaltsbereinigung vorausgewählt. Das kann nützlich sein, wenn Sie den rechtlichen Inhalt bereinigen möchten, bevor Sie ihn übersetzen."
-- Web content reader is hidden -- Web content reader is hidden
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T2799795311"] = "Der Web-Content-Reader zum Lesen von Webinhalten ist ausgeblendet" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T2799795311"] = "Der Web-Content-Reader zum Lesen von Webinhalten ist ausgeblendet"
@ -8044,7 +8065,7 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T3448155
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T3641773985"] = "Der Web-Content-Reader zum Lesen von Webinhalten ist vorausgewählt" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T3641773985"] = "Der Web-Content-Reader zum Lesen von Webinhalten ist vorausgewählt"
-- Preselect the content cleaner agent? -- Preselect the content cleaner agent?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T3649428096"] = "Assistent zur Inhaltsbereinigungs vorauswählen?" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T3649428096"] = "Agent zur Inhaltsbereinigung vorauswählen?"
-- Assistant: Legal Check Options -- Assistant: Legal Check Options
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T4033382756"] = "Assistent: Optionen für rechtliche Prüfung" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T4033382756"] = "Assistent: Optionen für rechtliche Prüfung"
@ -8350,10 +8371,10 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T354
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T3641773985"] = "Der Web-Content-Reader zum Lesen von Webinhalten ist vorausgewählt" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T3641773985"] = "Der Web-Content-Reader zum Lesen von Webinhalten ist vorausgewählt"
-- Preselect the content cleaner agent? -- Preselect the content cleaner agent?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T3649428096"] = "Den Agenten zur Inhaltsbereinigungs vorauswählen?" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T3649428096"] = "Agent zur Inhaltsbereinigung vorauswählen?"
-- When enabled, the content cleaner agent is preselected. This is might be useful when you prefer to clean up the content before summarize it. -- When enabled, the content cleaner agent is preselected. This is might be useful when you prefer to clean up the content before summarize it.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T3660434400"] = "Wenn diese Option aktiviert ist, wird der Content Cleaner-Agent automatisch vorausgewählt. Das kann nützlich sein, wenn Sie den Inhalt bereinigen möchten, bevor Sie ihn zusammenfassen." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T3660434400"] = "Wenn diese Option aktiviert ist, wird der Agent zur Inhaltsbereinigung automatisch vorausgewählt. Das kann nützlich sein, wenn Sie den Inhalt bereinigen möchten, bevor Sie ihn zusammenfassen."
-- Preselect important aspects -- Preselect important aspects
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T3705987833"] = "Vorauswahl der Aspekte" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T3705987833"] = "Vorauswahl der Aspekte"
@ -8446,7 +8467,7 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T629158
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T884246296"] = "Wie schnell soll die Live-Übersetzung reagieren?" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T884246296"] = "Wie schnell soll die Live-Übersetzung reagieren?"
-- When enabled, the content cleaner agent is preselected. This is might be useful when you prefer to clean up the content before translating it. -- When enabled, the content cleaner agent is preselected. This is might be useful when you prefer to clean up the content before translating it.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T894123480"] = "Wenn aktiviert, ist der Assistent zur Inhaltsbereinigung vorausgewählt. Das kann hilfreich sein, wenn Sie den Inhalt vor der Übersetzung bereinigen möchten." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T894123480"] = "Wenn aktiviert, ist der Agent zur Inhaltsbereinigung vorausgewählt. Das kann hilfreich sein, wenn Sie den Inhalt vor der Übersetzung bereinigen möchten."
-- Preselect live translation? -- Preselect live translation?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T918172772"] = "Live-Übersetzung vorauswählen?" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T918172772"] = "Live-Übersetzung vorauswählen?"
@ -10117,7 +10138,7 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCE::T3010553924"] = "Der Anbieter h
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCE::T3368531176"] = "Kein Anbieter ausgewählt. Bitte wählen Sie einen Anbieter aus, um dessen Vertrauensniveau zu sehen." UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCE::T3368531176"] = "Kein Anbieter ausgewählt. Bitte wählen Sie einen Anbieter aus, um dessen Vertrauensniveau zu sehen."
-- You or your organization operate this gateway. However, it forwards your data to **whichever providers you configured behind it**, which may be cloud services in any jurisdiction. We cannot know where your data ends up, so **please assign the trust level yourself**. -- You or your organization operate this gateway. However, it forwards your data to **whichever providers you configured behind it**, which may be cloud services in any jurisdiction. We cannot know where your data ends up, so **please assign the trust level yourself**.
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCE::T3370749159"] = "Sie oder Ihre Organisation betreiben dieses Gateway. Es leitet Ihre Daten jedoch an **die Anbieter weiter, die Sie dahinter konfiguriert haben**. Dabei kann es sich um Cloud-Dienste in beliebigen Rechtsräumen handeln. Wir können nicht wissen, wo Ihre Daten letztendlich landen. **Bitte legen Sie die Vertrauensstufe daher selbst fest.**" UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCE::T3370749159"] = "Sie oder Ihre Organisation betreiben dieses Gateway. Es leitet Ihre Daten jedoch an **die Anbieter weiter, die Sie dahinter konfiguriert haben**. Dabei kann es sich um Cloud-Dienste in beliebigen Rechtsräumen handeln. Wir können nicht wissen, wo Ihre Daten letztendlich landen. **Bitte legen Sie das Vertrauensniveau daher selbst fest.**"
-- The provider operates its service from the USA and is subject to **US jurisdiction**. In case of suspicion, authorities in the USA can access your data. However, **your data is not used for training** purposes. -- The provider operates its service from the USA and is subject to **US jurisdiction**. In case of suspicion, authorities in the USA can access your data. However, **your data is not used for training** purposes.
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCE::T3528165925"] = "Der Anbieter betreibt seinen Dienst aus den USA und unterliegt der **US-amerikanischen Gerichtsbarkeit**. Bei Verdacht können US-Behörden auf ihre Daten zugreifen. **Ihre Daten werden jedoch nicht für Trainingszwecke** verwendet." UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCE::T3528165925"] = "Der Anbieter betreibt seinen Dienst aus den USA und unterliegt der **US-amerikanischen Gerichtsbarkeit**. Bei Verdacht können US-Behörden auf ihre Daten zugreifen. **Ihre Daten werden jedoch nicht für Trainingszwecke** verwendet."

View File

@ -54,12 +54,18 @@ UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T2034826
-- The security check could not be completed because the LLM's 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." 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."
-- The provider is not trusted enough for security checks.
UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T2861409367"] = "The provider is not trusted enough for security checks."
-- The audit agent did not return a usable response. -- The audit agent did not return a usable response.
UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T3310188890"] = "The audit agent did not return a usable response." UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T3310188890"] = "The audit agent did not return a usable response."
-- No provider is configured for the 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." UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T3605554201"] = "No provider is configured for the Security Audit Agent."
-- The selected provider is not trusted enough for security checks. Pick one which meets the confidence required here, or choose a dedicated provider for security checks in the app settings.
UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T4104574219"] = "The selected provider is not trusted enough for security checks. Pick one which meets the confidence required here, or choose a dedicated provider for security checks in the app settings."
-- The audit result was empty. -- The audit result was empty.
UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T432419958"] = "The audit result was empty." UI_TEXT_CONTENT["AISTUDIO::AGENTS::ASSISTANTAUDIT::ASSISTANTAUDITAGENT::T432419958"] = "The audit result was empty."
@ -4293,15 +4299,18 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T3980535867"] = "Please
-- Attached file '{0}'. -- Attached file '{0}'.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T853724151"] = "Attached file '{0}'." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T853724151"] = "Attached file '{0}'."
-- The selected model does not meet the confidence requirements of the content cleaner. Please select another model, or configure an eligible one in the app settings.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T1028731446"] = "The selected model does not meet the confidence requirements of the content cleaner. Please select another model, or configure an eligible one in the app settings."
-- The content cleaner uses the model of this assistant. Please select one below.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T1160768613"] = "The content cleaner uses the model of this assistant. Please select one below."
-- The content is cleaned using an LLM agent: the main content is extracted, advertisements and other irrelevant things are attempted to be removed; relative links are attempted to be converted into absolute links so that they can be used. -- The content is cleaned using an LLM agent: the main content is extracted, advertisements and other irrelevant things are attempted to be removed; relative links are attempted to be converted into absolute links so that they can be used.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T1164201762"] = "The content is cleaned using an LLM agent: the main content is extracted, advertisements and other irrelevant things are attempted to be removed; relative links are attempted to be converted into absolute links so that they can be used." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T1164201762"] = "The content is cleaned using an LLM agent: the main content is extracted, advertisements and other irrelevant things are attempted to be removed; relative links are attempted to be converted into absolute links so that they can be used."
-- Fetch -- Fetch
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T1396322691"] = "Fetch" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T1396322691"] = "Fetch"
-- Please select a provider to use the cleanup agent.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T2035652317"] = "Please select a provider to use the cleanup agent."
-- Please provide a URL to load the content from. -- Please provide a URL to load the content from.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T2235427807"] = "Please provide a URL to load the content from." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T2235427807"] = "Please provide a URL to load the content from."
@ -4335,6 +4344,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T3825586228"] = "Please p
-- Show web content options -- Show web content options
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T4249712357"] = "Show web content options" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T4249712357"] = "Show web content options"
-- The content was loaded, but not cleaned: no model is available for the content cleaner.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T903778235"] = "The content was loaded, but not cleaned: no model is available for the content cleaner."
-- Loading -- Loading
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENTSTEPSEXTENSIONS::T1404011351"] = "Loading" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENTSTEPSEXTENSIONS::T1404011351"] = "Loading"
@ -5424,9 +5436,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1357418474"] =
-- No security issues were found during this check. -- No security issues were found during this check.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1423034104"] = "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 -- {0:0.##} KB
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T14914764"] = "{0:0.##} KB" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T14914764"] = "{0:0.##} KB"
@ -5466,6 +5475,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T1996966820"] =
-- Properties -- Properties
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2177370620"] = "Properties" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2177370620"] = "Properties"
-- Model
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2189814010"] = "Model"
-- Items: {0} -- Items: {0}
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2204150657"] = "Items: {0}" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2204150657"] = "Items: {0}"
@ -5475,12 +5487,12 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2562655035"] =
-- The assistant plugin could not be resolved for auditing. -- 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." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T273798258"] = "The assistant plugin could not be resolved for auditing."
-- Audit provider
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2757790517"] = "Audit provider"
-- Size -- Size
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2789707388"] = "Size" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2789707388"] = "Size"
-- No model configured
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T2941224401"] = "No model configured"
-- Prompt: set -- Prompt: set
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3156437951"] = "Prompt: set" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T3156437951"] = "Prompt: set"
@ -5529,6 +5541,12 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T4229995215"] =
-- 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? -- 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::T521056824"] = "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::T521056824"] = "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?"
-- Audit model
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T532102309"] = "Audit model"
-- No model is set for security checks, neither for this agent nor for the app as a whole. Choose one here to check this plugin. Your choice applies to this check only; you can set a permanent one in the app settings.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T614645381"] = "No model is set for security checks, neither for this agent nor for the app as a whole. Choose one here to check this plugin. Your choice applies to this check only; you can set a permanent one in the app settings."
-- System Prompt -- System Prompt
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T628396066"] = "System Prompt" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T628396066"] = "System Prompt"
@ -5541,6 +5559,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T760494712"] = "
-- Start Security Check -- Start Security Check
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T811648299"] = "Start Security Check" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T811648299"] = "Start Security Check"
-- Please select a model.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T818893091"] = "Please select a model."
-- Cancel -- Cancel
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T900713019"] = "Cancel" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::ASSISTANTPLUGINAUDITDIALOG::T900713019"] = "Cancel"

View File

@ -41,6 +41,8 @@
- Improved what happens when you open an embedding provider whose server is unreachable or no longer offers the model you chose. That model stays selected, and AI Studio tells you the server does not have it right now. The documents you already prepared keep working, and you are not asked to prepare them again over a change you never made. - Improved what happens when you open an embedding provider whose server is unreachable or no longer offers the model you chose. That model stays selected, and AI Studio tells you the server does not have it right now. The documents you already prepared keep working, and you are not asked to prepare them again over a change you never made.
- Improved the question AI Studio asks before you delete an embedding provider. It now names the data sources depending on that provider, together with what they can still do without it. - Improved the question AI Studio asks before you delete an embedding provider. It now names the data sources depending on that provider, together with what they can still do without it.
- Improved what the AI is told when it answers from your own documents (RAG): it now learns which page a passage came from, so it can name the page an answer rests on. - Improved what the AI is told when it answers from your own documents (RAG): it now learns which page a passage came from, so it can name the page an answer rests on.
- Improved what happens when you ask for web content to be cleaned up and no model is available for it. AI Studio loads the page and tells you it arrived uncleaned, instead of quietly handing you the raw page with its navigation and advertising still in it.
- Changed which model a security check of an assistant plugin may fall back on. When you have set none aside for these checks, AI Studio uses the model you were working with, but only when that model meets the trust your organization requires for a check. One ruled out for that purpose no longer gets to read a plugin's code.
- Changed how provider trust and provider confidence work together. Marking a provider as trustworthy in a configuration no longer also satisfies a required confidence level: one says who runs the provider, the other how confidential it is. Organizations raise a provider's level in their own confidence scheme instead. This applies beyond local data sources, for example, when a model reads a page from your intranet. - Changed how provider trust and provider confidence work together. Marking a provider as trustworthy in a configuration no longer also satisfies a required confidence level: one says who runs the provider, the other how confidential it is. Organizations raise a provider's level in their own confidence scheme instead. This applies beyond local data sources, for example, when a model reads a page from your intranet.
- Fixed the abilities AI Studio assumed for many models. We checked the families against their documentation: some models gained image input, reasoning, or tool calling, others lost an ability they never had. - Fixed the abilities AI Studio assumed for many models. We checked the families against their documentation: some models gained image input, reasoning, or tool calling, others lost an ability they never had.
- Fixed the Document Analysis assistant freezing while you edited a policy. It needed a change of yours to be saved in the background just as you were making the next one — picking a provider, for instance — which is why it hit some of you again and again and others never at all. - Fixed the Document Analysis assistant freezing while you edited a policy. It needed a change of yours to be saved in the background just as you were making the next one — picking a provider, for instance — which is why it hit some of you again and again and others never at all.
@ -58,11 +60,15 @@
- Fixed AI Studio shutting down without warning when two PDF files were read at the same time, e.g., when you previewed one while another was still being read in the background. - Fixed AI Studio shutting down without warning when two PDF files were read at the same time, e.g., when you previewed one while another was still being read in the background.
- Fixed the web address staying in the field when you reset an assistant that loads content from a web page. - Fixed the web address staying in the field when you reset an assistant that loads content from a web page.
- Fixed the web address being gone when you leave such an assistant and come back to it later. - Fixed the web address being gone when you leave such an assistant and come back to it later.
- Fixed the Visual Briefing Assistant (in preview) not scrolling, which put everything below the window edge out of reach and made the assistant unusable. The briefing preview is now shown at its intended size inside its frame, and switching between the desktop, tablet, and mobile view changes its width as it should. - Fixed an assistant refusing to work after you switched on the cleanup of web content without having chosen a model first. Its button did nothing at all, and only closing the assistant and starting over helped. The cleanup now follows the model of the assistant you are working in, the moment you pick one.
- Fixed the Visual Briefing assistant (in preview) not scrolling, which put everything below the window edge out of reach and made the assistant unusable. The briefing preview is now shown at its intended size inside its frame, and switching between the desktop, tablet, and mobile view changes its width as it should.
- Fixed exported answers losing their sources. When an answer is based on web pages a tool read or on documents of your own, the exported file now lists those sources in every format AI Studio writes. - Fixed exported answers losing their sources. When an answer is based on web pages a tool read or on documents of your own, the exported file now lists those sources in every format AI Studio writes.
- Fixed the copy button leaving the sources behind. Copy an answer, and its sources come along. - Fixed the copy button leaving the sources behind. Copy an answer, and its sources come along.
- Fixed a tile that opens a chat directly always demanding a workspace. Leave the workspace empty in the Assistant Builder, and the tile opens a disappearing chat instead. - Fixed a tile that opens a chat directly always demanding a workspace. Leave the workspace empty in the Assistant Builder, and the tile opens a disappearing chat instead.
- Fixed the same restriction for plugin authors: a direct-chat launcher can now open a chat without naming a workspace. The example assistant plugin shows both ways. - Fixed the same restriction for plugin authors: a direct-chat launcher can now open a chat without naming a workspace. The example assistant plugin shows both ways.
- Fixed the security check of an assistant plugin being impossible when no model is set aside for such checks, and you have no app-wide default either. The dialog now lets you pick one, and that choice applies to this one check. Before, the button to start the check was greyed out with nothing saying why, so the plugin could not be enabled at all.
- Fixed a security check that failed, leaving you no way to try again. When a check ends without a result, because the model could not be reached or a key was wrong, you can simply start it once more instead of closing the dialog and opening it anew.
- Fixed a security check that failed counting as a check that took place. Such a check no longer unlocks an assistant plugin for use, and it no longer replaces the last result that did say something about that plugin.
- Fixed data sources you picked for your chats vanishing from the selection without a word when they cannot be used. AI Studio now lists them by name, so you can see why an answer was created without them. - Fixed data sources you picked for your chats vanishing from the selection without a word when they cannot be used. AI Studio now lists them by name, so you can see why an answer was created without them.
- Fixed the data sources you picked for a chat being forgotten the moment you changed your selection while one of them could not be used. Such a source stays selected and is used again as soon as it is available. - Fixed the data sources you picked for a chat being forgotten the moment you changed your selection while one of them could not be used. Such a source stays selected and is used again as soon as it is available.
- Fixed the silence when the step that picks the fitting passages out of your documents fails. You are told that the answer rests on everything that was found. - Fixed the silence when the step that picks the fitting passages out of your documents fails. You are told that the answer rests on everything that was found.
@ -71,6 +77,6 @@
- Fixed the list of models staying empty at a server you host yourself, which made the model you had picked look as if it had vanished. Your key was there all along, it just was not read when the settings opened. - Fixed the list of models staying empty at a server you host yourself, which made the model you had picked look as if it had vanished. Your key was there all along, it just was not read when the settings opened.
- Fixed AI Studio asking such a server for its models with an empty key attached when you had stored none at all. Servers behind a login turn those requests down. - Fixed AI Studio asking such a server for its models with an empty key attached when you had stored none at all. Servers behind a login turn those requests down.
- Fixed a key that could not be saved going unmentioned for the servers you host yourself. You are now told what went wrong, instead of the settings simply staying open. - Fixed a key that could not be saved going unmentioned for the servers you host yourself. You are now told what went wrong, instead of the settings simply staying open.
- Fixed transcripts are quietly losing what was said softly, such as a greeting at the very beginning of a recording. AI Studio compressed recordings so far before sending them to your transcription provider that the model could no longer make out those passages. Recordings now keep enough details for the whole of what you said to arrive. - Fixed transcripts quietly losing what was said softly, such as a greeting at the very beginning of a recording. AI Studio compressed recordings so far before sending them to your transcription provider that the model could no longer make out those passages. Recordings now keep enough details for the whole of what you said to arrive.
- Upgraded the Visual Briefing Assistant (in preview) from the prototype to the beta state. The assistant is now completely implemented and is undergoing a deeper testing phase in preparation for release. To try it, open the app settings, allow preview features down to beta, and then enable the Visual Briefing Assistant there. - Upgraded the Visual Briefing assistant (in preview) from the prototype to the beta state. The assistant is now completely implemented and is undergoing a deeper testing phase in preparation for release. To try it, open the app settings, allow preview features down to beta, and then enable the Visual Briefing assistant there.
- Upgraded the vector database behind local RAG (Qdrant Edge) to version 0.8.0. - Upgraded the vector database behind local RAG (Qdrant Edge) to version 0.8.0.

View File

@ -0,0 +1,217 @@
using AIStudio.Provider;
using AIStudio.Settings;
using Microsoft.Extensions.Logging.Abstractions;
namespace AIStudio.Tests.Settings;
/// <summary>
/// Checks which provider an agent is handed when nobody picked one for it.
/// </summary>
/// <remarks>
/// Agents such as the content cleaner or the security audit may be given a model of their own,
/// because a small and cheap one is enough for what they do. Almost nobody does that, so what
/// matters in practice is what happens when they have none: the model of the assistant they sit
/// in, the app-wide default, or nothing at all. Two bugs shipped in that fallback, and both of
/// them lived here rather than in the components -- which is why this is where they are pinned
/// down. The component lifecycle itself is not covered; there is no bUnit in this solution.
///
/// Names are written out in full throughout. This assembly has an AIStudio.Tests.Tools and an
/// AIStudio.Tests.Provider of its own, and the app has an AIStudio.Components -- all three are
/// what a short name finds from here, and a using alias does not help, because names from the
/// enclosing namespaces win over it.
/// </remarks>
[TestFixture]
public sealed class PreselectedProviderTests
{
private const string ASSISTANT_PROVIDER_ID = "11111111-1111-1111-1111-111111111111";
private const string AGENT_PROVIDER_ID = "22222222-2222-2222-2222-222222222222";
private const string APP_DEFAULT_PROVIDER_ID = "33333333-3333-3333-3333-333333333333";
[Test]
public void ContentCleanerFallsBackToTheAssistantProvider()
{
var settingsManager = CreateSettingsManager();
AddProvider(settingsManager, ASSISTANT_PROVIDER_ID, LLMProviders.OPEN_AI);
AddProvider(settingsManager, AGENT_PROVIDER_ID, LLMProviders.MISTRAL);
var provider = settingsManager.GetPreselectedProvider(AIStudio.Tools.Components.AGENT_TEXT_CONTENT_CLEANER, ASSISTANT_PROVIDER_ID, true);
Assert.That(provider.Id, Is.EqualTo(ASSISTANT_PROVIDER_ID), "With nothing configured for the cleaner, it has to use the model of the assistant around it.");
}
[Test]
public void ContentCleanerFallsBackToTheAppDefault()
{
var settingsManager = CreateSettingsManager();
AddProvider(settingsManager, ASSISTANT_PROVIDER_ID, LLMProviders.OPEN_AI);
AddProvider(settingsManager, APP_DEFAULT_PROVIDER_ID, LLMProviders.MISTRAL);
settingsManager.ConfigurationData.App.PreselectedProvider = APP_DEFAULT_PROVIDER_ID;
var provider = settingsManager.GetPreselectedProvider(AIStudio.Tools.Components.AGENT_TEXT_CONTENT_CLEANER, null, true);
Assert.That(provider.Id, Is.EqualTo(APP_DEFAULT_PROVIDER_ID), "Without an assistant model, the app-wide default is what is left before giving up.");
}
[Test]
public void ContentCleanerPrefersItsOwnProviderOverTheAssistantOne()
{
var settingsManager = CreateSettingsManager();
AddProvider(settingsManager, ASSISTANT_PROVIDER_ID, LLMProviders.OPEN_AI);
AddProvider(settingsManager, AGENT_PROVIDER_ID, LLMProviders.MISTRAL);
settingsManager.ConfigurationData.TextContentCleaner.PreselectAgentOptions = true;
settingsManager.ConfigurationData.TextContentCleaner.PreselectedAgentProvider = AGENT_PROVIDER_ID;
var provider = settingsManager.GetPreselectedProvider(AIStudio.Tools.Components.AGENT_TEXT_CONTENT_CLEANER, ASSISTANT_PROVIDER_ID, true);
Assert.That(provider.Id, Is.EqualTo(AGENT_PROVIDER_ID), "A model picked for the cleaner is the whole point of picking one, so it outranks the assistant's.");
}
/// <summary>
/// Checks that the switch above the cleaner's provider field really turns that provider off.
/// </summary>
/// <remarks>
/// The provider id stays in the settings when the switch goes off, so the only thing saying it
/// must not be used is this one flag. A component which reads the stored id instead of asking
/// here would keep using a provider the user switched away from.
/// </remarks>
[Test]
public void ContentCleanerIgnoresItsOwnProviderWhenPreselectionIsOff()
{
var settingsManager = CreateSettingsManager();
AddProvider(settingsManager, ASSISTANT_PROVIDER_ID, LLMProviders.OPEN_AI);
AddProvider(settingsManager, AGENT_PROVIDER_ID, LLMProviders.MISTRAL);
settingsManager.ConfigurationData.TextContentCleaner.PreselectAgentOptions = false;
settingsManager.ConfigurationData.TextContentCleaner.PreselectedAgentProvider = AGENT_PROVIDER_ID;
var provider = settingsManager.GetPreselectedProvider(AIStudio.Tools.Components.AGENT_TEXT_CONTENT_CLEANER, ASSISTANT_PROVIDER_ID, true);
Assert.That(provider.Id, Is.EqualTo(ASSISTANT_PROVIDER_ID), "With its preselection switched off, the cleaner has to fall back to the assistant's model.");
}
/// <summary>
/// Checks that a model too untrusted for the cleaner is not used just because it is there.
/// </summary>
/// <remarks>
/// This is the case the user meets as a hint next to the cleaner switch: a model is selected in
/// the assistant, and the cleaner still has none. Under TRUST_ALL every provider reaches MEDIUM
/// and a self-hosted one reaches HIGH, so a global minimum of HIGH separates the two.
/// </remarks>
[Test]
public void ContentCleanerRejectsAnAssistantProviderBelowTheGlobalMinimum()
{
var settingsManager = CreateSettingsManager();
AddProvider(settingsManager, ASSISTANT_PROVIDER_ID, LLMProviders.OPEN_AI);
AddProvider(settingsManager, AGENT_PROVIDER_ID, LLMProviders.MISTRAL);
settingsManager.ConfigurationData.Confidence.EnforceGlobalMinimumConfidence = true;
settingsManager.ConfigurationData.Confidence.GlobalMinimumConfidence = ConfidenceLevel.HIGH;
var provider = settingsManager.GetPreselectedProvider(AIStudio.Tools.Components.AGENT_TEXT_CONTENT_CLEANER, ASSISTANT_PROVIDER_ID, true);
Assert.That(provider, Is.EqualTo(AIStudio.Settings.Provider.NONE), "A provider the organization ruled out must not reach the cleaner through the assistant.");
}
[Test]
public void ContentCleanerAcceptsAnAssistantProviderMeetingTheGlobalMinimum()
{
var settingsManager = CreateSettingsManager();
AddProvider(settingsManager, ASSISTANT_PROVIDER_ID, LLMProviders.SELF_HOSTED);
AddProvider(settingsManager, AGENT_PROVIDER_ID, LLMProviders.MISTRAL);
settingsManager.ConfigurationData.Confidence.EnforceGlobalMinimumConfidence = true;
settingsManager.ConfigurationData.Confidence.GlobalMinimumConfidence = ConfidenceLevel.HIGH;
var provider = settingsManager.GetPreselectedProvider(AIStudio.Tools.Components.AGENT_TEXT_CONTENT_CLEANER, ASSISTANT_PROVIDER_ID, true);
Assert.That(provider.Id, Is.EqualTo(ASSISTANT_PROVIDER_ID), "A provider which clears the bar has to be handed over, or the hint would never go away.");
}
/// <summary>
/// Checks that the audit agent's provider needs no switch to be used.
/// </summary>
/// <remarks>
/// Unlike the content cleaner, the audit agent has no "preselect options" flag: an organization
/// rolls its provider out and that is what audits run with. A test which assumed the two agents
/// behaved alike would pass here for the wrong reason.
/// </remarks>
[Test]
public void AuditAgentUsesItsOwnProviderWithoutAPreselectionSwitch()
{
var settingsManager = CreateSettingsManager();
AddProvider(settingsManager, AGENT_PROVIDER_ID, LLMProviders.MISTRAL);
AddProvider(settingsManager, APP_DEFAULT_PROVIDER_ID, LLMProviders.OPEN_AI);
settingsManager.ConfigurationData.App.PreselectedProvider = APP_DEFAULT_PROVIDER_ID;
settingsManager.ConfigurationData.AssistantPluginAudit.PreselectedAgentProvider = AGENT_PROVIDER_ID;
var provider = settingsManager.GetPreselectedProvider(AIStudio.Tools.Components.AGENT_ASSISTANT_PLUGIN_AUDIT, null, true);
Assert.That(provider.Id, Is.EqualTo(AGENT_PROVIDER_ID), "A provider rolled out for audits outranks the app-wide default.");
}
[Test]
public void AuditAgentFallsBackToTheAppDefault()
{
var settingsManager = CreateSettingsManager();
AddProvider(settingsManager, ASSISTANT_PROVIDER_ID, LLMProviders.OPEN_AI);
AddProvider(settingsManager, APP_DEFAULT_PROVIDER_ID, LLMProviders.MISTRAL);
settingsManager.ConfigurationData.App.PreselectedProvider = APP_DEFAULT_PROVIDER_ID;
var provider = settingsManager.GetPreselectedProvider(AIStudio.Tools.Components.AGENT_ASSISTANT_PLUGIN_AUDIT, null, true);
Assert.That(provider.Id, Is.EqualTo(APP_DEFAULT_PROVIDER_ID), "Without a dedicated audit provider, the app-wide default is what audits run with.");
}
/// <summary>
/// Checks the state the audit dialog was useless in before it offered a provider itself.
/// </summary>
[Test]
public void AuditAgentEndsUpWithNothingWhenNeitherIsConfigured()
{
var settingsManager = CreateSettingsManager();
AddProvider(settingsManager, ASSISTANT_PROVIDER_ID, LLMProviders.OPEN_AI);
AddProvider(settingsManager, AGENT_PROVIDER_ID, LLMProviders.MISTRAL);
var provider = settingsManager.GetPreselectedProvider(AIStudio.Tools.Components.AGENT_ASSISTANT_PLUGIN_AUDIT, null, true);
Assert.That(provider, Is.EqualTo(AIStudio.Settings.Provider.NONE), "Two configured providers are not a choice: without one being named for audits, the agent has none.");
}
[Test]
public void AuditAgentRejectsAnAppDefaultBelowTheGlobalMinimum()
{
var settingsManager = CreateSettingsManager();
AddProvider(settingsManager, ASSISTANT_PROVIDER_ID, LLMProviders.SELF_HOSTED);
AddProvider(settingsManager, APP_DEFAULT_PROVIDER_ID, LLMProviders.OPEN_AI);
settingsManager.ConfigurationData.App.PreselectedProvider = APP_DEFAULT_PROVIDER_ID;
settingsManager.ConfigurationData.Confidence.EnforceGlobalMinimumConfidence = true;
settingsManager.ConfigurationData.Confidence.GlobalMinimumConfidence = ConfidenceLevel.HIGH;
var provider = settingsManager.GetPreselectedProvider(AIStudio.Tools.Components.AGENT_ASSISTANT_PLUGIN_AUDIT, null, true);
Assert.That(provider, Is.EqualTo(AIStudio.Settings.Provider.NONE), "The app-wide default is not exempt from what the organization enforces.");
}
/// <summary>
/// Builds a settings manager the way these tests need it.
/// </summary>
/// <remarks>
/// The rust service is handed in as null on purpose: resolving a provider never asks it
/// anything. It reads the configured providers, the confidence scheme and the preselections,
/// all of which are plain settings. Should that change, the test says so by failing loudly
/// rather than by quietly measuring something else.
/// </remarks>
private static SettingsManager CreateSettingsManager() => new(NullLogger<SettingsManager>.Instance, null!);
/// <summary>
/// Adds a provider to the settings.
/// </summary>
/// <remarks>
/// Every test here configures at least two of them, and not for variety: with exactly one
/// configured provider, resolving takes a shortcut and returns it without looking at any
/// preselection. A single-provider test would pass no matter what the fallback does.
/// </remarks>
private static void AddProvider(SettingsManager settingsManager, string id, LLMProviders llmProvider)
{
var providers = settingsManager.ConfigurationData.Providers;
providers.Add(new((uint)providers.Count + 1, id, $"Instance {providers.Count + 1}", llmProvider, new("test-model", null)));
}
}