Added a way for organizations to prohibit providers (#1105)

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-10-22 18:57:12 +02:00
1 parent 052868b24e
commit 7ba5f01388
38 files changed
+1488 -629

No files matched your search

@@ -293,13 +293,13 @@ public partial class AssistantBatchProcessing : AssistantBaseCore<SettingsDialog
var minimumLevel = this.GetMinimumConfidenceLevel();
var policyProvider = this.SettingsManager.GetPreselectedProvider(this.Component, this.selectedPolicy.PreselectedProvider);
if (policyProvider != Settings.Provider.NONE
&& policyProvider.UsedLLMProvider.GetConfidence(this.SettingsManager).Level >= minimumLevel)
&& this.SettingsManager.IsProviderUsable(policyProvider.UsedLLMProvider, minimumLevel))
this.ProviderSettings = policyProvider;
else
{
var fallbackProvider = this.SettingsManager.GetPreselectedProvider(this.Component, usePreselectionBeforeCurrentProvider: true);
this.ProviderSettings = fallbackProvider != Settings.Provider.NONE
&& fallbackProvider.UsedLLMProvider.GetConfidence(this.SettingsManager).Level >= minimumLevel
&& this.SettingsManager.IsProviderUsable(fallbackProvider.UsedLLMProvider, minimumLevel)
? fallbackProvider
: Settings.Provider.NONE;
}
@@ -619,7 +619,7 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<NoSettingsPan
{
// Keep the current provider if it still satisfies the minimum confidence:
if (this.ProviderSettings != Settings.Provider.NONE &&
this.ProviderSettings.UsedLLMProvider.GetConfidence(this.SettingsManager).Level >= minimumLevel)
this.SettingsManager.IsProviderUsable(this.ProviderSettings.UsedLLMProvider, minimumLevel))
{
this.CurrentProfile = this.ResolveProfileSelection();
return;
@@ -628,7 +628,7 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<NoSettingsPan
// Try to apply the policy preselection:
var policyProvider = this.SettingsManager.GetProviderById(this.selectedPolicy.PreselectedProvider);
if (policyProvider != Settings.Provider.NONE && policyProvider.UsedLLMProvider.GetConfidence(this.SettingsManager).Level >= minimumLevel)
if (policyProvider != Settings.Provider.NONE && this.SettingsManager.IsProviderUsable(policyProvider.UsedLLMProvider, minimumLevel))
{
this.ProviderSettings = policyProvider;
this.CurrentProfile = this.ResolveProfileSelection();
@@ -637,7 +637,7 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<NoSettingsPan
var fallbackProvider = this.SettingsManager.GetPreselectedProvider(this.Component, this.ProviderSettings.Id);
if (fallbackProvider != Settings.Provider.NONE &&
fallbackProvider.UsedLLMProvider.GetConfidence(this.SettingsManager).Level < minimumLevel)
!this.SettingsManager.IsProviderUsable(fallbackProvider.UsedLLMProvider, minimumLevel))
fallbackProvider = Settings.Provider.NONE;
this.ProviderSettings = fallbackProvider;
@@ -3643,6 +3643,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1543974632"] = "The media
-- {0}, of which approx. {1} from tools
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1614399447"] = "{0}, of which approx. {1} from tools"
-- The use of this provider has been prohibited by your organization. Please select a different provider.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1807387355"] = "The use of this provider has been prohibited by your organization. Please select a different provider."
-- Type your input here...
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1849313532"] = "Type your input here..."
@@ -4708,6 +4711,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROVIDERSELECTION::T2685487365"] = "Image
-- Speech input possible
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROVIDERSELECTION::T3005724142"] = "Speech input possible"
-- The use of all your LLM providers has been prohibited by your organization. Add a different provider in the app settings.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROVIDERSELECTION::T3506708164"] = "The use of all your LLM providers has been prohibited by your organization. Add a different provider in the app settings."
-- Uses reasoning (thinking) by default
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROVIDERSELECTION::T3891860124"] = "Uses reasoning (thinking) by default"
@@ -5278,6 +5284,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCONFIDENCE::T45885
-- Not yet configured
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCONFIDENCE::T48051324"] = "Not yet configured"
-- The use of this provider has been prohibited by your organization. Therefore, it is untrusted, and you cannot change its confidence level.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCONFIDENCE::T489079668"] = "The use of this provider has been prohibited by your organization. Therefore, it is untrusted, and you cannot change its confidence level."
-- Do you want to always see how trustworthy your providers are? This way, you stay in control of which provider you send your data to. You can choose a common schema or configure the trust levels for each provider yourself.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCONFIDENCE::T700839804"] = "Do you want to always see how trustworthy your providers are? This way, you stay in control of which provider you send your data to. You can choose a common schema or configure the trust levels for each provider yourself."
@@ -5296,6 +5305,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T13870
-- Delete
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1469573738"] = "Delete"
-- The use of this embedding provider has been prohibited by your organization.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1509600117"] = "The use of this embedding provider has been prohibited by your organization."
-- Embed text
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1644934561"] = "Embed text"
@@ -5446,6 +5458,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T341592
-- Actions
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3865031940"] = "Actions"
-- The use of this provider has been prohibited by your organization.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T4229691938"] = "The use of this provider has been prohibited by your organization."
-- Delete LLM Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T4269256234"] = "Delete LLM Provider"
@@ -5509,6 +5524,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T16
-- Uses the provider-configured model
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1760715963"] = "Uses the provider-configured model"
-- The use of this transcription provider has been prohibited by your organization.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T2059509230"] = "The use of this transcription provider has been prohibited by your organization."
-- Add Transcription Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T2066315685"] = "Add Transcription Provider"
@@ -7633,6 +7651,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1727440780"] = "Ho
-- Load
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1756340745"] = "Load"
-- The use of this provider has been prohibited by your organization. Please select a different provider.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1807387355"] = "The use of this provider has been prohibited by your organization. Please select a different provider."
-- Update
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1847791252"] = "Update"
@@ -8158,6 +8179,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1727440780"] = "Hostname"
-- Always on
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1761671861"] = "Always on"
-- The use of this provider has been prohibited by your organization. Please select a different provider.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1807387355"] = "The use of this provider has been prohibited by your organization. Please select a different provider."
-- Reset
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T180921696"] = "Reset"
@@ -9937,6 +9961,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1727440780"] =
-- Load
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1756340745"] = "Load"
-- The use of this provider has been prohibited by your organization. Please select a different provider.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1807387355"] = "The use of this provider has been prohibited by your organization. Please select a different provider."
-- Update
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1847791252"] = "Update"
@@ -11980,6 +12007,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::AIJOBS::AIJOBSERVICE::T1578005752"] = "The sel
-- The selected provider is not allowed for this chat.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::AIJOBS::AIJOBSERVICE::T174545104"] = "The selected provider is not allowed for this chat."
-- The use of this provider has been prohibited by your organization. Please select a different provider.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::AIJOBS::AIJOBSERVICE::T1807387355"] = "The use of this provider has been prohibited by your organization. Please select a different provider."
-- The AI job failed. The message is: '{0}'
UI_TEXT_CONTENT["AISTUDIO::TOOLS::AIJOBS::AIJOBSERVICE::T237448388"] = "The AI job failed. The message is: '{0}'"
@@ -13825,6 +13855,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T1738200
-- The index of the data source '{0}' is missing. The data source stays out of your chats until its index was built anew. Use the repair action to start that.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T204783307"] = "The index of the data source '{0}' is missing. The data source stays out of your chats until its index was built anew. Use the repair action to start that."
-- The use of the embedding provider of this data source has been prohibited by your organization. Please select a different embedding provider in the settings of the data source.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T2092244746"] = "The use of the embedding provider of this data source has been prohibited by your organization. Please select a different embedding provider in the settings of the data source."
-- The selected embedding provider is not allowed to index this data source. The data source asks for the confidence level '{0}', while the embedding provider has '{1}'.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T2186533187"] = "The selected embedding provider is not allowed to index this data source. The data source asks for the confidence level '{0}', while the embedding provider has '{1}'."
@@ -14755,6 +14788,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T2222598306"
-- Please select your security policy.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T2250909198"] = "Please select your security policy."
-- The use of this embedding provider has been prohibited by your organization. Please select a different embedding provider.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T2376506873"] = "The use of this embedding provider has been prohibited by your organization. Please select a different embedding provider."
-- Please acknowledge that you are aware of the cloud transcription implications.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T2818048408"] = "Please acknowledge that you are aware of the cloud transcription implications."
@@ -10,6 +10,8 @@ public static class ChatThreadExtensions
/// Checks if the specified provider is allowed for the chat thread.
/// </summary>
/// <remarks>
/// A provider the organization prohibited is never allowed, whatever the chat thread.<br/><br/>
///
/// We don't check if the provider is allowed to use the data sources of the chat thread.
/// That kind of check is done when the available data sources are resolved.<br/><br/>
///
@@ -23,11 +25,16 @@ public static class ChatThreadExtensions
/// <returns>True, when the provider is allowed for the chat thread. False, otherwise.</returns>
public static bool IsLLMProviderAllowed<T>(this ChatThread? chatThread, T provider)
{
var settingsManager = Program.SERVICE_PROVIDER.GetRequiredService<SettingsManager>();
// A provider the organization prohibited is allowed in no chat, not even in a new one:
if (settingsManager.IsProviderProhibited(GetLLMProvider(provider)))
return false;
// No chat thread available means we have a new chat. That's fine:
if (chatThread is null)
return true;
var settingsManager = Program.SERVICE_PROVIDER.GetRequiredService<SettingsManager>();
var providerConfidence = provider switch
{
IProvider p => p.GetConfidenceLevel(settingsManager),
@@ -75,4 +82,12 @@ public static class ChatThreadExtensions
false => chatThread.DataSecurity is not DataSourceSecurity.SELF_HOSTED,
};
}
private static LLMProviders GetLLMProvider<T>(T provider) => provider switch
{
IProvider p => p.Provider,
AIStudio.Settings.Provider p => p.UsedLLMProvider,
_ => LLMProviders.NONE,
};
}
+1 -1
View File
@@ -149,7 +149,7 @@ public sealed class ContentText : IContent
if(!chatThread.IsLLMProviderAllowed(provider))
{
LOGGER.LogError("The provider is not allowed for this chat thread due to data security or confidence-level requirements. Skipping the AI process.");
LOGGER.LogError("The provider is not allowed for this chat thread due to a prohibition of the organization, data security, or confidence-level requirements. Skipping the AI process.");
await this.CompleteWithoutStreaming();
return chatThread;
}
@@ -157,7 +157,7 @@
@if (!this.ChatThread.IsLLMProviderAllowed(this.Provider))
{
<MudTooltip Text="@T("The selected provider is not allowed in this chat due to data security or confidence-level requirements.")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
<MudTooltip Text="@(this.SettingsManager.IsProviderProhibited(this.Provider.UsedLLMProvider) ? T("The use of this provider has been prohibited by your organization. Please select a different provider.") : T("The selected provider is not allowed in this chat due to data security or confidence-level requirements."))" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
<MudIconButton Icon="@Icons.Material.Filled.Error" Color="Color.Error"/>
</MudTooltip>
}
@@ -76,21 +76,25 @@ public partial class ProviderSelection : MSGComponentBase
/// Says why there is nothing to choose from, or nothing at all when that is not the user's doing.
/// </summary>
/// <remarks>
/// An empty list has two causes the user can act on, and they lead to different places in the
/// settings: there is no provider yet, or none of the configured ones reaches the confidence
/// this component asks for. Naming the wrong one sends the user looking in the wrong place --
/// a first start has nobody to blame for a confidence level it never set. A missing or invalid
/// component is a third case and neither of those: it is a defect, it was logged as one, and
/// any explanation offered to the user here would be a guess.
/// An empty list has three causes the user can act on, and they lead to different places in the
/// settings: there is no provider yet, the organization prohibited all of them, or none of the
/// configured ones reaches the confidence this component asks for. Naming the wrong one sends
/// the user looking in the wrong place -- a first start has nobody to blame for a confidence
/// level it never set. A missing or invalid component is another case and none of those: it is
/// a defect, it was logged as one, and any explanation offered to the user here would be a guess.
/// </remarks>
private string? GetEmptySelectionHint()
{
if (this.Component is null or Tools.Components.NONE)
return null;
if (!this.SettingsManager.GetAllProviders().Any(x => x.UsedLLMProvider is not LLMProviders.NONE))
var configuredProviders = this.SettingsManager.GetAllProviders().Where(x => x.UsedLLMProvider is not LLMProviders.NONE).ToList();
if (configuredProviders.Count == 0)
return this.T("No LLM providers are configured yet. Add a provider in the app settings.");
if (configuredProviders.All(x => this.SettingsManager.IsProviderProhibited(x.UsedLLMProvider)))
return this.T("The use of all your LLM providers has been prohibited by your organization. Add a different provider in the app settings.");
return this.T("No LLM providers meet the confidence requirements. Configure an eligible provider in the app settings.");
}
@@ -166,6 +170,17 @@ public partial class ProviderSelection : MSGComponentBase
await this.ProviderSettingsChanged.InvokeAsync(updatedProvider);
}
//
// A configuration may also take our provider out of reach, e.g., when the organization
// prohibits it or raises the minimum confidence. The list already leaves it out, so the
// selection must not keep showing it either:
//
if (this.Component is not (null or Tools.Components.NONE) && this.ProviderSettings != AIStudio.Settings.Provider.NONE && !this.GetAvailableProviders().Contains(this.ProviderSettings))
{
this.ProviderSettings = AIStudio.Settings.Provider.NONE;
await this.ProviderSettingsChanged.InvokeAsync(AIStudio.Settings.Provider.NONE);
}
this.StateHasChanged();
}
}
@@ -105,7 +105,7 @@ public partial class SettingsPanelApp : SettingsPanelBase
var minimumLevel = this.SettingsManager.GetMinimumConfidenceLevel(Tools.Components.APP_SETTINGS);
foreach (var provider in this.SettingsManager.GetAllTranscriptionProviders())
{
if (provider.UsedLLMProvider.GetConfidence(this.SettingsManager).Level >= minimumLevel)
if (this.SettingsManager.IsProviderUsable(provider.UsedLLMProvider, minimumLevel))
yield return new(provider.Name, provider.Id);
}
}
@@ -39,9 +39,15 @@
</MudTd>
<MudTd>
<MudMarkdown Value="@context.GetConfidence(this.SettingsManager).Description" MarkdownPipeline="Markdown.SAFE_MARKDOWN_PIPELINE"/>
@if (this.SettingsManager.IsProviderProhibited(context))
{
<MudAlert Severity="Severity.Warning" Dense="@true" Class="mt-2">
@T("The use of this provider has been prohibited by your organization. Therefore, it is untrusted, and you cannot change its confidence level.")
</MudAlert>
}
</MudTd>
<MudTd Style="vertical-align: top;">
<MudMenu StartIcon="@Icons.Material.Filled.Security" EndIcon="@Icons.Material.Filled.KeyboardArrowDown" Label="@this.GetCurrentConfidenceLevelName(context)" Variant="Variant.Filled" Style="@this.SetCurrentConfidenceLevelColorStyle(context)" Disabled="@this.IsCustomConfidenceSchemeLocked()">
<MudMenu StartIcon="@Icons.Material.Filled.Security" EndIcon="@Icons.Material.Filled.KeyboardArrowDown" Label="@this.GetCurrentConfidenceLevelName(context)" Variant="Variant.Filled" Style="@this.SetCurrentConfidenceLevelColorStyle(context)" Disabled="@this.IsConfidenceLevelLocked(context)">
@foreach (var confidenceLevel in Enum.GetValues<ConfidenceLevel>().OrderBy(n => n))
{
if(confidenceLevel is ConfidenceLevel.NONE or ConfidenceLevel.UNKNOWN)
@@ -7,30 +7,51 @@ public partial class SettingsPanelConfidence : SettingsPanelBase
{
private string GetCurrentConfidenceLevelName(LLMProviders llmProvider)
{
if (this.SettingsManager.ConfigurationData.Confidence.CustomConfidenceScheme.TryGetValue(llmProvider, out var level))
if (this.TryGetShownConfidenceLevel(llmProvider, out var level))
return level.GetName();
return T("Not yet configured");
}
private string SetCurrentConfidenceLevelColorStyle(LLMProviders llmProvider)
{
if (this.SettingsManager.ConfigurationData.Confidence.CustomConfidenceScheme.TryGetValue(llmProvider, out var level))
if (this.TryGetShownConfidenceLevel(llmProvider, out var level))
return $"background-color: {level.GetColor(this.SettingsManager)};";
return $"background-color: {ConfidenceLevel.UNKNOWN.GetColor(this.SettingsManager)};";
}
/// <summary>
/// Determines the confidence level the table shows for a provider.
/// </summary>
/// <remarks>
/// A prohibited provider is untrusted, whatever level was chosen for it, see
/// SettingsManager.GetConfiguredConfidenceLevel. The chosen level stays stored, so the table
/// shows it again once the prohibition ends.
/// </remarks>
private bool TryGetShownConfidenceLevel(LLMProviders llmProvider, out ConfidenceLevel level)
{
if (this.SettingsManager.IsProviderProhibited(llmProvider))
{
level = ConfidenceLevel.UNTRUSTED;
return true;
}
return this.SettingsManager.ConfigurationData.Confidence.CustomConfidenceScheme.TryGetValue(llmProvider, out level);
}
private bool IsCustomConfidenceSchemeLocked()
{
return ManagedConfiguration.TryGet(x => x.Confidence, x => x.CustomConfidenceScheme, out var meta) && meta.IsLocked;
}
private bool IsConfidenceLevelLocked(LLMProviders llmProvider) => this.IsCustomConfidenceSchemeLocked() || this.SettingsManager.IsProviderProhibited(llmProvider);
private async Task ChangeCustomConfidenceLevel(LLMProviders llmProvider, ConfidenceLevel level)
{
if (this.IsCustomConfidenceSchemeLocked())
if (this.IsConfidenceLevelLocked(llmProvider))
return;
this.SettingsManager.ConfigurationData.Confidence.CustomConfidenceScheme[llmProvider] = level;
await this.SettingsManager.StoreSettings();
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
@@ -44,6 +44,12 @@
<MudTd>
<MudStack Row="true" Class="mb-2 mt-2" Spacing="1" Wrap="Wrap.Wrap">
@if (this.SettingsManager.IsProviderProhibited(context.UsedLLMProvider))
{
<MudTooltip Text="@T("The use of this embedding provider has been prohibited by your organization.")">
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Block" Disabled="true"/>
</MudTooltip>
}
@if (context.IsTrustedForDataSourceSecurityChecks(this.SettingsManager))
{
<MudTooltip Text="@(context.IsSelfHosted ? T("This self-hosted embedding provider is trusted for data source security checks. Local data can be sent to it without security warnings.") : T("This embedding provider is trusted by your organization for data source security checks. Local data can be sent to it without security warnings."))">
@@ -62,7 +68,7 @@
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Key" OnClick="@(() => this.EditEmbeddingProvider(context))"/>
</MudTooltip>
<MudTooltip Text="@T("Test")">
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Api" OnClick="@(() => this.TestEmbeddingProvider(context))"/>
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Api" OnClick="@(() => this.TestEmbeddingProvider(context))" Disabled="@this.SettingsManager.IsProviderProhibited(context.UsedLLMProvider)"/>
</MudTooltip>
}
else
@@ -78,7 +84,7 @@
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Delete" OnClick="@(() => this.DeleteEmbeddingProvider(context))"/>
</MudTooltip>
<MudTooltip Text="@T("Test")">
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Api" OnClick="@(() => this.TestEmbeddingProvider(context))"/>
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Api" OnClick="@(() => this.TestEmbeddingProvider(context))" Disabled="@this.SettingsManager.IsProviderProhibited(context.UsedLLMProvider)"/>
</MudTooltip>
}
</MudStack>
@@ -36,6 +36,12 @@
<MudTd>@this.GetLLMProviderModelName(context)</MudTd>
<MudTd>
<MudStack Row="true" Class="mb-2 mt-2" Spacing="1" Wrap="Wrap.Wrap">
@if (this.SettingsManager.IsProviderProhibited(context.UsedLLMProvider))
{
<MudTooltip Text="@T("The use of this provider has been prohibited by your organization.")">
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Block" Disabled="true"/>
</MudTooltip>
}
@if (context.IsTrustedForDataSourceSecurityChecks(this.SettingsManager))
{
<MudTooltip Text="@(context.IsSelfHosted ? T("This self-hosted provider is trusted for data source security checks.") : T("This provider is trusted by your organization for data source security checks."))">
@@ -41,6 +41,12 @@
<MudTd>
<MudStack Row="true" Class="mb-2 mt-2" Spacing="1" Wrap="Wrap.Wrap">
@if (this.SettingsManager.IsProviderProhibited(context.UsedLLMProvider))
{
<MudTooltip Text="@T("The use of this transcription provider has been prohibited by your organization.")">
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Block" Disabled="true"/>
</MudTooltip>
}
@if (context.IsTrustedForDataSourceSecurityChecks(this.SettingsManager))
{
<MudTooltip Text="@(context.IsSelfHosted ? T("This self-hosted transcription provider is trusted for data source security checks.") : T("This transcription provider is trusted by your organization for data source security checks."))">
@@ -25,194 +25,203 @@
}
}
</MudSelect>
<MudButton Disabled="@(!this.DataLLMProvider.ShowRegisterButton())" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.DataLLMProvider.GetCreationURL()" Target="_blank">
<MudButton Disabled="@(!this.DataLLMProvider.ShowRegisterButton() || this.IsProviderProhibited)" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.DataLLMProvider.GetCreationURL()" Target="_blank">
@T("Create account")
</MudButton>
</MudStack>
@if (this.DataLLMProvider.IsAPIKeyNeeded(this.DataHost))
@if (this.IsProviderProhibited)
{
<SecretInputField Secret="@this.dataAPIKey" SecretChanged="@this.OnAPIKeyChanged" Label="@this.APIKeyText" Validation="@this.providerValidation.ValidatingAPIKey"/>
<MudAlert Severity="Severity.Warning" Class="mb-3">
@T("The use of this provider has been prohibited by your organization. Please select a different provider.")
</MudAlert>
}
@if (this.DataLLMProvider.IsHostnameNeeded())
else
{
<MudTextField
T="string"
@bind-Text="@this.DataHostname"
Label="@T("Hostname")"
Class="mb-3"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Dns"
AdornmentColor="Color.Info"
Disabled="@this.IsEnterpriseConfiguration"
Validation="@this.providerValidation.ValidatingHostname"
UserAttributes="@SPELLCHECK_ATTRIBUTES"/>
}
@if (this.DataLLMProvider.IsAPIKeyNeeded(this.DataHost))
{
<SecretInputField Secret="@this.dataAPIKey" SecretChanged="@this.OnAPIKeyChanged" Label="@this.APIKeyText" Validation="@this.providerValidation.ValidatingAPIKey"/>
}
@if (this.DataLLMProvider.IsHostNeeded())
{
<MudSelect T="Host" Value="@this.DataHost" ValueChanged="@this.OnHostChanged" Label="@T("Host")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Disabled="@this.IsEnterpriseConfiguration" Validation="@this.providerValidation.ValidatingHost">
@foreach (Host host in Enum.GetValues(typeof(Host)))
{
if (host.IsEmbeddingSupported())
@if (this.DataLLMProvider.IsHostnameNeeded())
{
<MudTextField
T="string"
@bind-Text="@this.DataHostname"
Label="@T("Hostname")"
Class="mb-3"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Dns"
AdornmentColor="Color.Info"
Disabled="@this.IsEnterpriseConfiguration"
Validation="@this.providerValidation.ValidatingHostname"
UserAttributes="@SPELLCHECK_ATTRIBUTES"/>
}
@if (this.DataLLMProvider.IsHostNeeded())
{
<MudSelect T="Host" Value="@this.DataHost" ValueChanged="@this.OnHostChanged" Label="@T("Host")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Disabled="@this.IsEnterpriseConfiguration" Validation="@this.providerValidation.ValidatingHost">
@foreach (Host host in Enum.GetValues(typeof(Host)))
{
<MudSelectItem Value="@host">
@host.Name()
</MudSelectItem>
}
}
</MudSelect>
}
@if (this.DataLLMProvider.IsHFInstanceProviderNeeded())
{
<MudSelect T="HFInferenceProvider" Value="@this.HFInferenceProviderId" ValueChanged="@this.OnHFInferenceProviderChanged" Label="@T("Hugging Face Inference Provider")" Class="mb-3" OpenIcon="@Icons.Material.Filled.Dns" AdornmentColor="Color.Info" Adornment="Adornment.Start" Disabled="@this.IsEnterpriseConfiguration" Validation="@this.providerValidation.ValidatingHFInstanceProviderForEmbeddings">
@foreach (HFInferenceProvider inferenceProvider in Enum.GetValues(typeof(HFInferenceProvider)))
{
@if (inferenceProvider.SupportsEmbeddings())
{
<MudSelectItem Value="@inferenceProvider">
@inferenceProvider.ToName()
</MudSelectItem>
}
}
</MudSelect>
<MudJustifiedText Class="mb-3">
@T("Hugging Face offers embeddings through a few of its inference providers only, which is why this list is shorter than the one for chatting.")
</MudJustifiedText>
}
<MudField FullWidth="true" Label="@T("Model selection")" Variant="Variant.Outlined" Class="mb-3">
<MudStack Row="@true" AlignItems="AlignItems.Center" StretchItems="StretchItems.End">
<MudButton Disabled="@(this.IsEnterpriseConfiguration || !this.DataLLMProvider.CanLoadModels(this.DataHost, this.dataAPIKey))" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="@this.ReloadModels">
@T("Load")
</MudButton>
@if (this.availableModels.Count is 0)
{
<MudText Typo="Typo.body1">
@T("No models loaded or available.")
</MudText>
}
else
{
<MudSelect Disabled="@(this.IsEnterpriseConfiguration || this.IsNoneProvider)" @bind-Value="@this.DataModel" Label="@T("Model")"
OpenIcon="@Icons.Material.Filled.FaceRetouchingNatural"
AdornmentColor="Color.Info" Adornment="Adornment.Start"
Validation="@this.providerValidation.ValidatingModel">
@foreach (var model in this.availableModels)
if (host.IsEmbeddingSupported())
{
<MudSelectItem Value="@model">
@model
<MudSelectItem Value="@host">
@host.Name()
</MudSelectItem>
}
</MudSelect>
}
</MudStack>
@if (this.dataConfiguredModelIsNotOffered)
{
<MudAlert Severity="Severity.Info" Class="mt-3">
@T("This server does not offer the selected model right now. It stays selected, so the documents you already prepared keep working. Choosing another model means every document of the data sources behind this provider is prepared again.")
</MudAlert>
}
</MudSelect>
}
@if (this.ServerNamedNoEmbeddingModel)
{
<MudAlert Severity="Severity.Info" Class="mt-3">
@T("Your server answered, but none of the models it serves is one we know to create embeddings. Either there is none installed, or it runs under a name we do not recognize. In the latter case, your organization can describe the model in a model plugin, and it will show up here.")
</MudAlert>
}
@if (!string.IsNullOrWhiteSpace(this.dataLoadingModelsIssue))
{
<MudAlert Severity="Severity.Error" Class="mt-3">
@this.dataLoadingModelsIssue
</MudAlert>
}
</MudField>
@* ReSharper disable once CSharpWarnings::CS8974 *@
<MudTextField
T="string"
@bind-Text="@this.DataName"
Label="@T("Instance Name")"
Class="mb-3"
MaxLength="40"
Counter="40"
Immediate="@true"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Lightbulb"
AdornmentColor="Color.Info"
Disabled="@this.IsEnterpriseConfiguration"
Validation="@this.providerValidation.ValidatingInstanceName"
UserAttributes="@SPELLCHECK_ATTRIBUTES"/>
@if (this.DataLLMProvider != LLMProviders.NONE)
{
<MudStack>
<MudButton OnClick="@this.ToggleExpertSettings">
@(this.showExpertSettings ? T("Hide Expert Settings") : T("Show Expert Settings"))
</MudButton>
<MudDivider />
<MudCollapse Expanded="@this.showExpertSettings" Class="@this.GetExpertStyles">
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
@T("Please be aware: This section is for experts only. For cloud providers, the selected tokenizer and chunk settings may not match the real embedding model limits exactly.")
</MudJustifiedText>
<MudNumericField
T="int"
@bind-Value="@this.DataTokenLimit"
Label="@T("Token limit")"
Class="mb-3"
Min="1"
Immediate="@true"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Numbers"
AdornmentColor="Color.Info"
Disabled="@this.IsEnterpriseConfiguration"
Validation="@this.ValidateTokenLimit"
HelperText="@T("Maximum number of tokens sent to the embedding model per chunk. The default is 8,192.")"/>
<MudNumericField
T="int"
@bind-Value="@this.DataEmbeddingBatchSize"
Label="@T("Embedding batch size")"
Class="mb-3"
Min="1"
Immediate="@true"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.FormatListNumbered"
AdornmentColor="Color.Info"
Disabled="@this.IsEnterpriseConfiguration"
Validation="@this.ValidateEmbeddingBatchSize"
HelperText="@T("How many chunks are sent to the embedding provider at once. The default is 1.")"/>
<TokenizerHint LLMProvider="@this.DataLLMProvider" Model="@this.DataModel"/>
<PathDropZone IdPrefix="tokenizer" Disabled="@(() => this.IsEnterpriseConfiguration)" OnPathsDropped="@this.OnTokenizerPathsDropped">
<MudStack Row="@true" Spacing="3" Class="mb-3" StretchItems="StretchItems.None" AlignItems="AlignItems.Center">
<MudTextField
T="string"
Text="@this.dataFilePath"
TextChanged="@this.OnDataFilePathChanged"
Label="@T("Selected file path for the custom tokenizer")"
Disabled="@this.IsEnterpriseConfiguration"
Validation="@this.providerValidation.ValidatingCustomTokenizer"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.AttachFile"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
Variant="Variant.Outlined"
Clearable="@true"
Error="@(!string.IsNullOrWhiteSpace(this.dataCustomTokenizerValidationIssue))"
ErrorText="@this.dataCustomTokenizerValidationIssue"
OnClearButtonClick="@this.ClearPathTokenizer"
/>
<MudButton StartIcon="@Icons.Material.Filled.FolderOpen" Variant="Variant.Outlined" Color="Color.Primary" Disabled="@(this.IsEnterpriseConfiguration || this.isTokenizerFileDialogOpen)" OnClick="@this.OpenTokenizerFileDialog">
@T("Choose File")
</MudButton>
</MudStack>
<MudText Typo="Typo.body2">
@T("You can also drag & drop the tokenizer file here.")
@if (this.DataLLMProvider.IsHFInstanceProviderNeeded())
{
<MudSelect T="HFInferenceProvider" Value="@this.HFInferenceProviderId" ValueChanged="@this.OnHFInferenceProviderChanged" Label="@T("Hugging Face Inference Provider")" Class="mb-3" OpenIcon="@Icons.Material.Filled.Dns" AdornmentColor="Color.Info" Adornment="Adornment.Start" Disabled="@this.IsEnterpriseConfiguration" Validation="@this.providerValidation.ValidatingHFInstanceProviderForEmbeddings">
@foreach (HFInferenceProvider inferenceProvider in Enum.GetValues(typeof(HFInferenceProvider)))
{
@if (inferenceProvider.SupportsEmbeddings())
{
<MudSelectItem Value="@inferenceProvider">
@inferenceProvider.ToName()
</MudSelectItem>
}
}
</MudSelect>
<MudJustifiedText Class="mb-3">
@T("Hugging Face offers embeddings through a few of its inference providers only, which is why this list is shorter than the one for chatting.")
</MudJustifiedText>
}
<MudField FullWidth="true" Label="@T("Model selection")" Variant="Variant.Outlined" Class="mb-3">
<MudStack Row="@true" AlignItems="AlignItems.Center" StretchItems="StretchItems.End">
<MudButton Disabled="@(this.IsEnterpriseConfiguration || !this.DataLLMProvider.CanLoadModels(this.DataHost, this.dataAPIKey))" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="@this.ReloadModels">
@T("Load")
</MudButton>
@if (this.availableModels.Count is 0)
{
<MudText Typo="Typo.body1">
@T("No models loaded or available.")
</MudText>
</PathDropZone>
</MudCollapse>
</MudStack>
}
else
{
<MudSelect Disabled="@(this.IsEnterpriseConfiguration || this.IsNoneProvider)" @bind-Value="@this.DataModel" Label="@T("Model")"
OpenIcon="@Icons.Material.Filled.FaceRetouchingNatural"
AdornmentColor="Color.Info" Adornment="Adornment.Start"
Validation="@this.providerValidation.ValidatingModel">
@foreach (var model in this.availableModels)
{
<MudSelectItem Value="@model">
@model
</MudSelectItem>
}
</MudSelect>
}
</MudStack>
@if (this.dataConfiguredModelIsNotOffered)
{
<MudAlert Severity="Severity.Info" Class="mt-3">
@T("This server does not offer the selected model right now. It stays selected, so the documents you already prepared keep working. Choosing another model means every document of the data sources behind this provider is prepared again.")
</MudAlert>
}
@if (this.ServerNamedNoEmbeddingModel)
{
<MudAlert Severity="Severity.Info" Class="mt-3">
@T("Your server answered, but none of the models it serves is one we know to create embeddings. Either there is none installed, or it runs under a name we do not recognize. In the latter case, your organization can describe the model in a model plugin, and it will show up here.")
</MudAlert>
}
@if (!string.IsNullOrWhiteSpace(this.dataLoadingModelsIssue))
{
<MudAlert Severity="Severity.Error" Class="mt-3">
@this.dataLoadingModelsIssue
</MudAlert>
}
</MudField>
@* ReSharper disable once CSharpWarnings::CS8974 *@
<MudTextField
T="string"
@bind-Text="@this.DataName"
Label="@T("Instance Name")"
Class="mb-3"
MaxLength="40"
Counter="40"
Immediate="@true"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Lightbulb"
AdornmentColor="Color.Info"
Disabled="@this.IsEnterpriseConfiguration"
Validation="@this.providerValidation.ValidatingInstanceName"
UserAttributes="@SPELLCHECK_ATTRIBUTES"/>
@if (this.DataLLMProvider != LLMProviders.NONE)
{
<MudStack>
<MudButton OnClick="@this.ToggleExpertSettings">
@(this.showExpertSettings ? T("Hide Expert Settings") : T("Show Expert Settings"))
</MudButton>
<MudDivider />
<MudCollapse Expanded="@this.showExpertSettings" Class="@this.GetExpertStyles">
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
@T("Please be aware: This section is for experts only. For cloud providers, the selected tokenizer and chunk settings may not match the real embedding model limits exactly.")
</MudJustifiedText>
<MudNumericField
T="int"
@bind-Value="@this.DataTokenLimit"
Label="@T("Token limit")"
Class="mb-3"
Min="1"
Immediate="@true"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Numbers"
AdornmentColor="Color.Info"
Disabled="@this.IsEnterpriseConfiguration"
Validation="@this.ValidateTokenLimit"
HelperText="@T("Maximum number of tokens sent to the embedding model per chunk. The default is 8,192.")"/>
<MudNumericField
T="int"
@bind-Value="@this.DataEmbeddingBatchSize"
Label="@T("Embedding batch size")"
Class="mb-3"
Min="1"
Immediate="@true"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.FormatListNumbered"
AdornmentColor="Color.Info"
Disabled="@this.IsEnterpriseConfiguration"
Validation="@this.ValidateEmbeddingBatchSize"
HelperText="@T("How many chunks are sent to the embedding provider at once. The default is 1.")"/>
<TokenizerHint LLMProvider="@this.DataLLMProvider" Model="@this.DataModel"/>
<PathDropZone IdPrefix="tokenizer" Disabled="@(() => this.IsEnterpriseConfiguration)" OnPathsDropped="@this.OnTokenizerPathsDropped">
<MudStack Row="@true" Spacing="3" Class="mb-3" StretchItems="StretchItems.None" AlignItems="AlignItems.Center">
<MudTextField
T="string"
Text="@this.dataFilePath"
TextChanged="@this.OnDataFilePathChanged"
Label="@T("Selected file path for the custom tokenizer")"
Disabled="@this.IsEnterpriseConfiguration"
Validation="@this.providerValidation.ValidatingCustomTokenizer"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.AttachFile"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
Variant="Variant.Outlined"
Clearable="@true"
Error="@(!string.IsNullOrWhiteSpace(this.dataCustomTokenizerValidationIssue))"
ErrorText="@this.dataCustomTokenizerValidationIssue"
OnClearButtonClick="@this.ClearPathTokenizer"
/>
<MudButton StartIcon="@Icons.Material.Filled.FolderOpen" Variant="Variant.Outlined" Color="Color.Primary" Disabled="@(this.IsEnterpriseConfiguration || this.isTokenizerFileDialogOpen)" OnClick="@this.OpenTokenizerFileDialog">
@T("Choose File")
</MudButton>
</MudStack>
<MudText Typo="Typo.body2">
@T("You can also drag & drop the tokenizer file here.")
</MudText>
</PathDropZone>
</MudCollapse>
</MudStack>
}
}
</MudForm>
@if (this.dataStoreWasAttempted)
@if (this.dataStoreWasAttempted && !this.IsProviderProhibited)
{
<Issues IssuesData="@this.dataIssues"/>
}
@@ -221,7 +230,7 @@
<MudButton OnClick="@this.Cancel" Variant="Variant.Filled">
@T("Cancel")
</MudButton>
<MudButton OnClick="@this.Store" Variant="Variant.Filled" Color="Color.Primary">
<MudButton OnClick="@this.Store" Variant="Variant.Filled" Color="Color.Primary" Disabled="@this.IsProviderProhibited">
@if (this.IsEditing)
{
@T("Update")
@@ -135,7 +135,16 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
/// The list of used instance names. We need this to check for uniqueness.
/// </summary>
private List<string> UsedInstanceNames { get; set; } = [];
/// <summary>
/// Whether the organization prohibited the selected provider.
/// </summary>
/// <remarks>
/// The provider stays selectable, so that nobody wonders where it went. The dialog then shows
/// the reason instead of the form, and nothing can be stored.
/// </remarks>
private bool IsProviderProhibited => this.SettingsManager.IsProviderProhibited(this.DataLLMProvider);
private bool dataIsValid;
private string[] dataIssues = [];
private string dataAPIKey = string.Empty;
@@ -284,6 +293,9 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
private async Task Store()
{
if (this.IsProviderProhibited)
return;
this.dataStoreWasAttempted = true;
await this.dataTokenizerValidationTask;
await this.form.Validate();
@@ -544,6 +556,10 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
private async Task ReloadModels()
{
// Loading the models would contact the provider:
if (this.IsProviderProhibited)
return;
this.dataLoadingModelsIssue = string.Empty;
this.dataServerWasAskedForItsModels = true;
var currentEmbeddingProviderSettings = this.CreateEmbeddingProviderSettings();
@@ -27,326 +27,338 @@
</MudSelectItem>
}
</MudSelect>
<MudButton Disabled="@(!this.DataLLMProvider.ShowRegisterButton())" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.DataLLMProvider.GetCreationURL()" Target="_blank">
<MudButton Disabled="@(!this.DataLLMProvider.ShowRegisterButton() || this.IsProviderProhibited)" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.DataLLMProvider.GetCreationURL()" Target="_blank">
@T("Create account")
</MudButton>
</MudStack>
@if (this.DataLLMProvider.IsAPIKeyNeeded(this.DataHost))
@if (this.IsProviderProhibited)
{
<SecretInputField Secret="@this.dataAPIKey" SecretChanged="@this.OnAPIKeyChanged" Label="@this.APIKeyText" Validation="@this.providerValidation.ValidatingAPIKey"/>
<MudAlert Severity="Severity.Warning" Class="mb-3">
@T("The use of this provider has been prohibited by your organization. Please select a different provider.")
</MudAlert>
}
@if (this.DataLLMProvider.IsHostnameNeeded())
else
{
<MudTextField
T="string"
@bind-Text="@this.DataHostname"
Label="@T("Hostname")"
Class="mb-3"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Dns"
AdornmentColor="Color.Info"
Disabled="@this.IsEnterpriseConfiguration"
Validation="@this.providerValidation.ValidatingHostname"
UserAttributes="@SPELLCHECK_ATTRIBUTES"/>
}
@if (this.DataLLMProvider.IsAPIKeyNeeded(this.DataHost))
{
<SecretInputField Secret="@this.dataAPIKey" SecretChanged="@this.OnAPIKeyChanged" Label="@this.APIKeyText" Validation="@this.providerValidation.ValidatingAPIKey"/>
}
@if (this.DataLLMProvider.IsHostNeeded())
{
<MudSelect T="Host" Value="@this.DataHost" ValueChanged="@this.OnHostChanged" Label="@T("Host")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Disabled="@this.IsEnterpriseConfiguration" Validation="@this.providerValidation.ValidatingHost">
@foreach (Host host in Enum.GetValues(typeof(Host)))
{
@if (host.IsChatSupported())
@if (this.DataLLMProvider.IsHostnameNeeded())
{
<MudTextField
T="string"
@bind-Text="@this.DataHostname"
Label="@T("Hostname")"
Class="mb-3"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Dns"
AdornmentColor="Color.Info"
Disabled="@this.IsEnterpriseConfiguration"
Validation="@this.providerValidation.ValidatingHostname"
UserAttributes="@SPELLCHECK_ATTRIBUTES"/>
}
@if (this.DataLLMProvider.IsHostNeeded())
{
<MudSelect T="Host" Value="@this.DataHost" ValueChanged="@this.OnHostChanged" Label="@T("Host")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Disabled="@this.IsEnterpriseConfiguration" Validation="@this.providerValidation.ValidatingHost">
@foreach (Host host in Enum.GetValues(typeof(Host)))
{
<MudSelectItem Value="@host">
@host.Name()
</MudSelectItem>
@if (host.IsChatSupported())
{
<MudSelectItem Value="@host">
@host.Name()
</MudSelectItem>
}
}
}
</MudSelect>
}
</MudSelect>
}
@if (this.DataLLMProvider.IsHFInstanceProviderNeeded())
{
<MudSelect T="HFInferenceProvider" Value="@this.HFInferenceProviderId" ValueChanged="@this.OnHFInferenceProviderChanged" Label="@T("Hugging Face Inference Provider")" Class="mb-3" OpenIcon="@Icons.Material.Filled.Dns" AdornmentColor="Color.Info" Adornment="Adornment.Start" Disabled="@this.IsEnterpriseConfiguration" Validation="@this.providerValidation.ValidatingHFInstanceProvider">
@foreach (HFInferenceProvider inferenceProvider in Enum.GetValues(typeof(HFInferenceProvider)))
{
@if (inferenceProvider.SupportsChat())
@if (this.DataLLMProvider.IsHFInstanceProviderNeeded())
{
<MudSelect T="HFInferenceProvider" Value="@this.HFInferenceProviderId" ValueChanged="@this.OnHFInferenceProviderChanged" Label="@T("Hugging Face Inference Provider")" Class="mb-3" OpenIcon="@Icons.Material.Filled.Dns" AdornmentColor="Color.Info" Adornment="Adornment.Start" Disabled="@this.IsEnterpriseConfiguration" Validation="@this.providerValidation.ValidatingHFInstanceProvider">
@foreach (HFInferenceProvider inferenceProvider in Enum.GetValues(typeof(HFInferenceProvider)))
{
<MudSelectItem Value="@inferenceProvider">
@inferenceProvider.ToName()
</MudSelectItem>
@if (inferenceProvider.SupportsChat())
{
<MudSelectItem Value="@inferenceProvider">
@inferenceProvider.ToName()
</MudSelectItem>
}
}
}
</MudSelect>
<MudJustifiedText Class="mb-3">
@T("Choose which inference provider should answer your requests. When you pick one of the automatic options instead, Hugging Face selects a provider for you and switches to another one when your choice is unavailable.")
</MudJustifiedText>
}
</MudSelect>
<MudJustifiedText Class="mb-3">
@T("Choose which inference provider should answer your requests. When you pick one of the automatic options instead, Hugging Face selects a provider for you and switches to another one when your choice is unavailable.")
</MudJustifiedText>
}
@if (!this.IsLLMModelSelectionHidden)
{
<MudField FullWidth="true" Label="@T("Model selection")" Variant="Variant.Outlined" Class="mb-3">
<MudStack Row="@true" AlignItems="AlignItems.Center" StretchItems="StretchItems.End">
@if (this.DataLLMProvider.IsLLMModelProvidedManually())
{
<MudButton Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.ModelsOverviewURL" Target="_blank">
@T("Show available models")
</MudButton>
<MudTextField
T="string"
@bind-Text="@this.dataManuallyModel"
Label="@T("Model")"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.FaceRetouchingNatural"
AdornmentColor="Color.Info"
Disabled="@this.IsEnterpriseConfiguration"
Validation="@this.ValidateManuallyModel"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
HelperText="@T("Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually.")"
/>
}
else
{
@if (!string.IsNullOrWhiteSpace(this.ModelsOverviewURL))
@if (!this.IsLLMModelSelectionHidden)
{
<MudField FullWidth="true" Label="@T("Model selection")" Variant="Variant.Outlined" Class="mb-3">
<MudStack Row="@true" AlignItems="AlignItems.Center" StretchItems="StretchItems.End">
@if (this.DataLLMProvider.IsLLMModelProvidedManually())
{
<MudButton Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.ModelsOverviewURL" Target="_blank">
@T("Show available models")
</MudButton>
}
<MudButton Disabled="@(this.IsEnterpriseConfiguration || !this.DataLLMProvider.CanLoadModels(this.DataHost, this.dataAPIKey))" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="@this.ReloadModels">
@T("Load models")
</MudButton>
@if(this.availableModels.Count is 0)
{
<MudText Typo="Typo.body1">
@T("No models loaded or available.")
</MudText>
<MudTextField
T="string"
@bind-Text="@this.dataManuallyModel"
Label="@T("Model")"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.FaceRetouchingNatural"
AdornmentColor="Color.Info"
Disabled="@this.IsEnterpriseConfiguration"
Validation="@this.ValidateManuallyModel"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
HelperText="@T("Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually.")"
/>
}
else
{
<MudSelect T="Provider.Model"
Value="@this.DataModel"
ValueChanged="@(async model => await this.OnModelChanged(model))"
OpenIcon="@Icons.Material.Filled.FaceRetouchingNatural" AdornmentColor="Color.Info"
Adornment="Adornment.Start" Disabled="@this.IsEnterpriseConfiguration" Validation="@this.providerValidation.ValidatingModel">
@foreach (var model in this.availableModels)
@if (!string.IsNullOrWhiteSpace(this.ModelsOverviewURL))
{
<MudButton Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.ModelsOverviewURL" Target="_blank">
@T("Show available models")
</MudButton>
}
<MudButton Disabled="@(this.IsEnterpriseConfiguration || !this.DataLLMProvider.CanLoadModels(this.DataHost, this.dataAPIKey))" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="@this.ReloadModels">
@T("Load models")
</MudButton>
@if(this.availableModels.Count is 0)
{
<MudText Typo="Typo.body1">
@T("No models loaded or available.")
</MudText>
}
else
{
<MudSelect T="Provider.Model"
Value="@this.DataModel"
ValueChanged="@(async model => await this.OnModelChanged(model))"
OpenIcon="@Icons.Material.Filled.FaceRetouchingNatural" AdornmentColor="Color.Info"
Adornment="Adornment.Start" Disabled="@this.IsEnterpriseConfiguration" Validation="@this.providerValidation.ValidatingModel">
@foreach (var model in this.availableModels)
{
<MudSelectItem Value="@model">
@model
</MudSelectItem>
}
</MudSelect>
}
}
</MudStack>
@if (!string.IsNullOrWhiteSpace(this.dataLoadingModelsIssue))
{
<MudAlert Severity="Severity.Error" Class="mt-3">
@this.dataLoadingModelsIssue
</MudAlert>
}
</MudField>
}
else
{
<MudField FullWidth="true" Label="@T("Model selection")" Variant="Variant.Outlined" Class="mb-3">
<MudText Typo="Typo.body1">
@T("This host uses the model configured at the provider level. No model selection is available.")
</MudText>
</MudField>
}
@* ReSharper disable once CSharpWarnings::CS8974 *@
<MudTextField
T="string"
@bind-Text="@this.DataInstanceName"
Label="@T("Instance Name")"
Class="mb-3"
MaxLength="40"
Counter="40"
Immediate="@true"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Lightbulb"
AdornmentColor="Color.Info"
Disabled="@this.IsEnterpriseConfiguration"
Validation="@this.providerValidation.ValidatingInstanceName"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
/>
<MudStack>
<MudButton OnClick="@this.ToggleExpertSettings">
@(this.showExpertSettings ? T("Hide Expert Settings") : T("Show Expert Settings"))
</MudButton>
<MudDivider />
<MudCollapse Expanded="@this.showExpertSettings" Class="@this.GetExpertStyles">
<MudAlert Severity="Severity.Warning" Class="mb-4">
@T("Please be careful: wrong expert settings can break model usage, disable supported features, or make unsupported features appear available.")
</MudAlert>
<MudText Typo="Typo.subtitle2" Class="mb-2">
@T("Override Model Capabilities")
</MudText>
<MudJustifiedText Typo="Typo.body2" Class="mb-4">
@T("Stored default model capabilities may not reflect its full range. Override them here if needed.")
</MudJustifiedText>
<MudStack Class="mb-4" Spacing="2">
@foreach (var capability in SWITCH_CAPABILITY_OVERRIDES)
{
<MudPaper Outlined="@true" Class="pa-3">
<MudStack Row="@true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Wrap="Wrap.Wrap">
<MudStack Spacing="0">
<MudText Typo="Typo.body2">
@this.GetCapabilityOverrideLabel(capability)
</MudText>
<MudText Typo="Typo.caption" Color="Color.Secondary">
@this.GetCapabilityEffectiveLabel(capability)
</MudText>
</MudStack>
<MudStack Row="@true" AlignItems="AlignItems.Center" Spacing="2">
<MudSwitch T="bool"
Value="@this.IsCapabilityEnabled(capability)"
ValueChanged="@(value => this.OnCapabilitySwitchChanged(capability, value))"
Disabled="@this.IsEnterpriseConfiguration"
Color="Color.Primary" />
<MudButton Variant="Variant.Outlined"
Color="Color.Default"
Size="Size.Small"
StartIcon="@Icons.Material.Filled.RestartAlt"
Disabled="@(this.IsEnterpriseConfiguration || !this.HasCapabilityOverride(capability))"
OnClick="@(() => this.ResetCapabilityOverride(capability))">
@T("Reset")
</MudButton>
</MudStack>
</MudStack>
</MudPaper>
}
<MudPaper Outlined="@true" Class="pa-3">
<MudSelect T="ReasoningOverrideMode"
Value="@this.GetReasoningOverrideMode()"
ValueChanged="@this.SetReasoningOverrideMode"
Label="@T("Reasoning (thinking) behavior")"
HelperText="@this.GetReasoningOverrideModeDescription(this.GetReasoningOverrideMode())"
Variant="Variant.Outlined"
Margin="Margin.Dense"
OpenIcon="@Icons.Material.Filled.Psychology"
AdornmentColor="Color.Info"
Adornment="Adornment.Start"
Disabled="@this.IsEnterpriseConfiguration">
@foreach (var mode in REASONING_OVERRIDE_MODES)
{
<MudSelectItem Value="@model">
@model
<MudSelectItem Value="@mode">
@this.GetReasoningOverrideModeLabel(mode)
</MudSelectItem>
}
</MudSelect>
}
}
</MudStack>
@if (!string.IsNullOrWhiteSpace(this.dataLoadingModelsIssue))
{
<MudAlert Severity="Severity.Error" Class="mt-3">
@this.dataLoadingModelsIssue
</MudAlert>
}
</MudField>
}
else
{
<MudField FullWidth="true" Label="@T("Model selection")" Variant="Variant.Outlined" Class="mb-3">
<MudText Typo="Typo.body1">
@T("This host uses the model configured at the provider level. No model selection is available.")
</MudText>
</MudField>
}
@* ReSharper disable once CSharpWarnings::CS8974 *@
<MudTextField
T="string"
@bind-Text="@this.DataInstanceName"
Label="@T("Instance Name")"
Class="mb-3"
MaxLength="40"
Counter="40"
Immediate="@true"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Lightbulb"
AdornmentColor="Color.Info"
Disabled="@this.IsEnterpriseConfiguration"
Validation="@this.providerValidation.ValidatingInstanceName"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
/>
<MudStack>
<MudButton OnClick="@this.ToggleExpertSettings">
@(this.showExpertSettings ? T("Hide Expert Settings") : T("Show Expert Settings"))
</MudButton>
<MudDivider />
<MudCollapse Expanded="@this.showExpertSettings" Class="@this.GetExpertStyles">
<MudAlert Severity="Severity.Warning" Class="mb-4">
@T("Please be careful: wrong expert settings can break model usage, disable supported features, or make unsupported features appear available.")
</MudAlert>
<MudText Typo="Typo.subtitle2" Class="mb-2">
@T("Override Model Capabilities")
</MudText>
<MudJustifiedText Typo="Typo.body2" Class="mb-4">
@T("Stored default model capabilities may not reflect its full range. Override them here if needed.")
</MudJustifiedText>
<MudStack Class="mb-4" Spacing="2">
@foreach (var capability in SWITCH_CAPABILITY_OVERRIDES)
{
<MudPaper Outlined="@true" Class="pa-3">
<MudStack Row="@true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Wrap="Wrap.Wrap">
<MudStack Spacing="0">
<MudText Typo="Typo.body2">
@this.GetCapabilityOverrideLabel(capability)
</MudText>
<MudText Typo="Typo.caption" Color="Color.Secondary">
@this.GetCapabilityEffectiveLabel(capability)
</MudText>
</MudStack>
<MudStack Row="@true" AlignItems="AlignItems.Center" Spacing="2">
<MudSwitch T="bool"
Value="@this.IsCapabilityEnabled(capability)"
ValueChanged="@(value => this.OnCapabilitySwitchChanged(capability, value))"
Disabled="@this.IsEnterpriseConfiguration"
Color="Color.Primary" />
<MudButton Variant="Variant.Outlined"
Color="Color.Default"
Size="Size.Small"
StartIcon="@Icons.Material.Filled.RestartAlt"
Disabled="@(this.IsEnterpriseConfiguration || !this.HasCapabilityOverride(capability))"
OnClick="@(() => this.ResetCapabilityOverride(capability))">
@T("Reset")
</MudButton>
</MudStack>
</MudStack>
</MudPaper>
}
<MudPaper Outlined="@true" Class="pa-3">
<MudSelect T="ReasoningOverrideMode"
Value="@this.GetReasoningOverrideMode()"
ValueChanged="@this.SetReasoningOverrideMode"
Label="@T("Reasoning (thinking) behavior")"
HelperText="@this.GetReasoningOverrideModeDescription(this.GetReasoningOverrideMode())"
Variant="Variant.Outlined"
Margin="Margin.Dense"
OpenIcon="@Icons.Material.Filled.Psychology"
AdornmentColor="Color.Info"
Adornment="Adornment.Start"
Disabled="@this.IsEnterpriseConfiguration">
@foreach (var mode in REASONING_OVERRIDE_MODES)
{
<MudSelectItem Value="@mode">
@this.GetReasoningOverrideModeLabel(mode)
</MudSelectItem>
}
</MudSelect>
</MudPaper>
</MudStack>
<MudText Typo="Typo.subtitle2" Class="mb-2">
@T("Override Model Limits")
</MudText>
<MudJustifiedText Typo="Typo.body2" Class="mb-4">
@T("Where the stored numbers do not match your installation, state yours here. This matters most for self-hosted models: they run with whatever their operator configured, which the model card cannot know.")
</MudJustifiedText>
<MudStack Class="mb-4" Spacing="2">
<MudPaper Outlined="@true" Class="pa-3">
<MudNumericField T="int?"
Value="@this.capabilityOverrides.ContextWindowTokens"
ValueChanged="@this.SetContextWindowOverride"
Label="@T("Context window in tokens")"
HelperText="@this.ContextWindowHelperText"
Placeholder="@this.AutomaticContextWindowPlaceholder"
Variant="Variant.Outlined"
Margin="Margin.Dense"
Min="1"
HideSpinButtons="@true"
Clearable="@true"
Disabled="@this.IsEnterpriseConfiguration"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Straighten"
AdornmentColor="Color.Info" />
</MudPaper>
<MudPaper Outlined="@true" Class="pa-3">
<MudText Typo="Typo.body2">
@T("Images")
</MudText>
<MudText Typo="Typo.caption" Color="Color.Secondary">
@this.ImageLimitsEffectiveLabel
</MudText>
<MudStack Row="@true" Spacing="3" Wrap="Wrap.Wrap" Class="border-dashed border rounded-lg pa-3 mt-3">
<MudNumericField T="int?"
Value="@this.capabilityOverrides.MaxImagesPerMessage"
ValueChanged="@this.SetMaxImagesPerMessageOverride"
Label="@T("Per message")"
Placeholder="@this.AutomaticImageLimitPlaceholder(this.GetAutomaticModelProfile().Images.MaxPerMessage)"
Variant="Variant.Outlined"
Margin="Margin.Dense"
Min="0"
Clearable="@true"
Disabled="@this.IsEnterpriseConfiguration" />
<MudNumericField T="int?"
Value="@this.capabilityOverrides.MaxImagesPerRequest"
ValueChanged="@this.SetMaxImagesPerRequestOverride"
Label="@T("Per request")"
Placeholder="@this.AutomaticImageLimitPlaceholder(this.GetAutomaticModelProfile().Images.MaxPerRequest)"
Variant="Variant.Outlined"
Margin="Margin.Dense"
Min="0"
Clearable="@true"
Disabled="@this.IsEnterpriseConfiguration" />
</MudStack>
<MudText Typo="Typo.caption" Color="Color.Secondary" Class="mt-2">
@T("Vendors state one of the two, the other, or neither. Whichever is smaller decides how many images one message may carry; an empty field states nothing.")
</MudText>
</MudPaper>
</MudStack>
<MudJustifiedText Typo="Typo.body2" Class="mb-4">
@string.Format(T("The current model uses the {0}."), this.GetCurrentModelApiLabel())
</MudJustifiedText>
<MudTextField T="string" Label=@T("Additional API parameters") Variant="Variant.Outlined" Lines="4" AutoGrow="true" MaxLines="10" HelperText=@T("""Add the parameters in proper JSON formatting, e.g., "temperature": 0.5. Remove trailing commas. The usual surrounding curly brackets {} must not be used, though.""") Placeholder="@GetPlaceholderExpertSettings" @bind-Value="@this.AdditionalJsonApiParameters" Immediate="true" Disabled="@this.IsEnterpriseConfiguration" Validation="@this.ValidateAdditionalJsonApiParameters" OnBlur="@this.OnInputChangeExpertSettings"/>
@if (this.ShowTokenizerSettings)
{
<MudJustifiedText Typo="Typo.body1" Class="mt-4 mb-3">
@T("For better token estimates, you can configure a custom tokenizer for this provider.")
</MudStack>
<MudText Typo="Typo.subtitle2" Class="mb-2">
@T("Override Model Limits")
</MudText>
<MudJustifiedText Typo="Typo.body2" Class="mb-4">
@T("Where the stored numbers do not match your installation, state yours here. This matters most for self-hosted models: they run with whatever their operator configured, which the model card cannot know.")
</MudJustifiedText>
<TokenizerHint LLMProvider="@this.DataLLMProvider" Model="@this.DataModel"/>
<PathDropZone IdPrefix="tokenizer" Disabled="@(() => this.IsEnterpriseConfiguration)" OnPathsDropped="@this.OnTokenizerPathsDropped">
<MudStack Row="@true" Spacing="3" Class="mb-3" StretchItems="StretchItems.None" AlignItems="AlignItems.Center">
<MudTextField
T="string"
Text="@this.dataFilePath"
TextChanged="@this.OnDataFilePathChanged"
Label="@T("Selected file path for the custom tokenizer")"
Disabled="@this.IsEnterpriseConfiguration"
Validation="@this.providerValidation.ValidatingCustomTokenizer"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.AttachFile"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
Variant="Variant.Outlined"
Clearable="@true"
Error="@(!string.IsNullOrWhiteSpace(this.dataCustomTokenizerValidationIssue))"
ErrorText="@this.dataCustomTokenizerValidationIssue"
OnClearButtonClick="@this.ClearPathTokenizer"
/>
<MudButton StartIcon="@Icons.Material.Filled.FolderOpen" Variant="Variant.Outlined" Color="Color.Primary" Disabled="@(this.IsEnterpriseConfiguration || this.isTokenizerFileDialogOpen)" OnClick="@this.OpenTokenizerFileDialog">
@T("Choose File")
</MudButton>
</MudStack>
<MudText Typo="Typo.body2">
@T("You can also drag & drop the tokenizer file here.")
</MudText>
</PathDropZone>
}
</MudCollapse>
</MudStack>
<MudStack Class="mb-4" Spacing="2">
<MudPaper Outlined="@true" Class="pa-3">
<MudNumericField T="int?"
Value="@this.capabilityOverrides.ContextWindowTokens"
ValueChanged="@this.SetContextWindowOverride"
Label="@T("Context window in tokens")"
HelperText="@this.ContextWindowHelperText"
Placeholder="@this.AutomaticContextWindowPlaceholder"
Variant="Variant.Outlined"
Margin="Margin.Dense"
Min="1"
HideSpinButtons="@true"
Clearable="@true"
Disabled="@this.IsEnterpriseConfiguration"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Straighten"
AdornmentColor="Color.Info" />
</MudPaper>
<MudPaper Outlined="@true" Class="pa-3">
<MudText Typo="Typo.body2">
@T("Images")
</MudText>
<MudText Typo="Typo.caption" Color="Color.Secondary">
@this.ImageLimitsEffectiveLabel
</MudText>
<MudStack Row="@true" Spacing="3" Wrap="Wrap.Wrap" Class="border-dashed border rounded-lg pa-3 mt-3">
<MudNumericField T="int?"
Value="@this.capabilityOverrides.MaxImagesPerMessage"
ValueChanged="@this.SetMaxImagesPerMessageOverride"
Label="@T("Per message")"
Placeholder="@this.AutomaticImageLimitPlaceholder(this.GetAutomaticModelProfile().Images.MaxPerMessage)"
Variant="Variant.Outlined"
Margin="Margin.Dense"
Min="0"
Clearable="@true"
Disabled="@this.IsEnterpriseConfiguration" />
<MudNumericField T="int?"
Value="@this.capabilityOverrides.MaxImagesPerRequest"
ValueChanged="@this.SetMaxImagesPerRequestOverride"
Label="@T("Per request")"
Placeholder="@this.AutomaticImageLimitPlaceholder(this.GetAutomaticModelProfile().Images.MaxPerRequest)"
Variant="Variant.Outlined"
Margin="Margin.Dense"
Min="0"
Clearable="@true"
Disabled="@this.IsEnterpriseConfiguration" />
</MudStack>
<MudText Typo="Typo.caption" Color="Color.Secondary" Class="mt-2">
@T("Vendors state one of the two, the other, or neither. Whichever is smaller decides how many images one message may carry; an empty field states nothing.")
</MudText>
</MudPaper>
</MudStack>
<MudJustifiedText Typo="Typo.body2" Class="mb-4">
@string.Format(T("The current model uses the {0}."), this.GetCurrentModelApiLabel())
</MudJustifiedText>
<MudTextField T="string" Label=@T("Additional API parameters") Variant="Variant.Outlined" Lines="4" AutoGrow="true" MaxLines="10" HelperText=@T("""Add the parameters in proper JSON formatting, e.g., "temperature": 0.5. Remove trailing commas. The usual surrounding curly brackets {} must not be used, though.""") Placeholder="@GetPlaceholderExpertSettings" @bind-Value="@this.AdditionalJsonApiParameters" Immediate="true" Disabled="@this.IsEnterpriseConfiguration" Validation="@this.ValidateAdditionalJsonApiParameters" OnBlur="@this.OnInputChangeExpertSettings"/>
@if (this.ShowTokenizerSettings)
{
<MudJustifiedText Typo="Typo.body1" Class="mt-4 mb-3">
@T("For better token estimates, you can configure a custom tokenizer for this provider.")
</MudJustifiedText>
<TokenizerHint LLMProvider="@this.DataLLMProvider" Model="@this.DataModel"/>
<PathDropZone IdPrefix="tokenizer" Disabled="@(() => this.IsEnterpriseConfiguration)" OnPathsDropped="@this.OnTokenizerPathsDropped">
<MudStack Row="@true" Spacing="3" Class="mb-3" StretchItems="StretchItems.None" AlignItems="AlignItems.Center">
<MudTextField
T="string"
Text="@this.dataFilePath"
TextChanged="@this.OnDataFilePathChanged"
Label="@T("Selected file path for the custom tokenizer")"
Disabled="@this.IsEnterpriseConfiguration"
Validation="@this.providerValidation.ValidatingCustomTokenizer"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.AttachFile"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
Variant="Variant.Outlined"
Clearable="@true"
Error="@(!string.IsNullOrWhiteSpace(this.dataCustomTokenizerValidationIssue))"
ErrorText="@this.dataCustomTokenizerValidationIssue"
OnClearButtonClick="@this.ClearPathTokenizer"
/>
<MudButton StartIcon="@Icons.Material.Filled.FolderOpen" Variant="Variant.Outlined" Color="Color.Primary" Disabled="@(this.IsEnterpriseConfiguration || this.isTokenizerFileDialogOpen)" OnClick="@this.OpenTokenizerFileDialog">
@T("Choose File")
</MudButton>
</MudStack>
<MudText Typo="Typo.body2">
@T("You can also drag & drop the tokenizer file here.")
</MudText>
</PathDropZone>
}
</MudCollapse>
</MudStack>
}
</MudForm>
<Issues IssuesData="@this.dataIssues"/>
@if (!this.IsProviderProhibited)
{
<Issues IssuesData="@this.dataIssues"/>
}
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Cancel" Variant="Variant.Filled">
@T("Cancel")
</MudButton>
<MudButton OnClick="@this.Store" Variant="Variant.Filled" Color="Color.Primary">
<MudButton OnClick="@this.Store" Variant="Variant.Filled" Color="Color.Primary" Disabled="@this.IsProviderProhibited">
@if(this.IsEditing)
{
@T("Update")
@@ -153,7 +153,16 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
/// The list of used instance names. We need this to check for uniqueness.
/// </summary>
private List<string> UsedInstanceNames { get; set; } = [];
/// <summary>
/// Whether the organization prohibited the selected provider.
/// </summary>
/// <remarks>
/// The provider stays selectable, so that nobody wonders where it went. The dialog then shows
/// the reason instead of the form, and nothing can be stored.
/// </remarks>
private bool IsProviderProhibited => this.SettingsManager.IsProviderProhibited(this.DataLLMProvider);
private bool dataIsValid;
private string[] dataIssues = [];
private string dataAPIKey = string.Empty;
@@ -333,6 +342,9 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
private async Task Store()
{
if (this.IsProviderProhibited)
return;
this.dataStoreWasAttempted = true;
await this.dataTokenizerValidationTask;
await this.form.Validate();
@@ -593,6 +605,10 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
private async Task ReloadModels()
{
// Loading the models would contact the provider:
if (this.IsProviderProhibited)
return;
this.dataLoadingModelsIssue = string.Empty;
var currentProviderSettings = this.CreateProviderSettings();
var provider = currentProviderSettings.CreateProvider();
@@ -25,134 +25,146 @@
}
}
</MudSelect>
<MudButton Disabled="@(!this.DataLLMProvider.ShowRegisterButton())" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.DataLLMProvider.GetCreationURL()" Target="_blank">
<MudButton Disabled="@(!this.DataLLMProvider.ShowRegisterButton() || this.IsProviderProhibited)" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.DataLLMProvider.GetCreationURL()" Target="_blank">
@T("Create account")
</MudButton>
</MudStack>
@if (this.DataLLMProvider.IsAPIKeyNeeded(this.DataHost))
{
<SecretInputField Secret="@this.dataAPIKey" SecretChanged="@this.OnAPIKeyChanged" Label="@this.APIKeyText" Validation="@this.providerValidation.ValidatingAPIKey"/>
}
@if (this.DataLLMProvider.IsHostnameNeeded())
@if (this.IsProviderProhibited)
{
<MudTextField
T="string"
@bind-Text="@this.DataHostname"
Label="@T("Hostname")"
Class="mb-3"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Dns"
AdornmentColor="Color.Info"
Disabled="@this.IsEnterpriseConfiguration"
Validation="@this.providerValidation.ValidatingHostname"
UserAttributes="@SPELLCHECK_ATTRIBUTES"/>
}
@if (this.DataLLMProvider.IsHostNeeded())
{
<MudSelect T="Host" Value="@this.DataHost" ValueChanged="@this.OnHostChanged" Label="@T("Host")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Disabled="@this.IsEnterpriseConfiguration" Validation="@this.providerValidation.ValidatingHost">
@foreach (Host host in Enum.GetValues(typeof(Host)))
{
if (host.IsTranscriptionSupported())
{
<MudSelectItem Value="@host">
@host.Name()
</MudSelectItem>
}
}
</MudSelect>
}
@if (this.DataLLMProvider.IsHFInstanceProviderNeeded())
{
<MudSelect T="HFInferenceProvider" Value="@this.HFInferenceProviderId" ValueChanged="@this.OnHFInferenceProviderChanged" Label="@T("Hugging Face Inference Provider")" Class="mb-3" OpenIcon="@Icons.Material.Filled.Dns" AdornmentColor="Color.Info" Adornment="Adornment.Start" Disabled="@this.IsEnterpriseConfiguration" Validation="@this.providerValidation.ValidatingHFInstanceProviderForTranscription">
@foreach (HFInferenceProvider inferenceProvider in Enum.GetValues(typeof(HFInferenceProvider)))
{
@if (inferenceProvider.SupportsTranscription())
{
<MudSelectItem Value="@inferenceProvider">
@inferenceProvider.ToName()
</MudSelectItem>
}
}
</MudSelect>
<MudJustifiedText Class="mb-3">
@T("Hugging Face transcribes audio through a few of its inference providers only, which is why this list is shorter than the one for chatting.")
</MudJustifiedText>
}
@if (!this.DataLLMProvider.IsTranscriptionModelSelectionHidden(this.DataHost))
{
<MudField FullWidth="true" Label="@T("Model selection")" Variant="Variant.Outlined" Class="mb-3">
<MudStack Row="@true" AlignItems="AlignItems.Center" StretchItems="StretchItems.End">
<MudButton Disabled="@(this.IsEnterpriseConfiguration || !this.DataLLMProvider.CanLoadModels(this.DataHost, this.dataAPIKey))" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="@this.ReloadModels">
@T("Load")
</MudButton>
@if(this.availableModels.Count is 0)
{
<MudText Typo="Typo.body1">
@T("No models loaded or available.")
</MudText>
}
else
{
<MudSelect Disabled="@(this.IsEnterpriseConfiguration || this.IsNoneProvider)" @bind-Value="@this.DataModel" Label="@T("Model")"
OpenIcon="@Icons.Material.Filled.FaceRetouchingNatural"
AdornmentColor="Color.Info" Adornment="Adornment.Start"
Validation="@this.providerValidation.ValidatingModel">
@foreach (var model in this.availableModels)
{
<MudSelectItem Value="@model">
@model
</MudSelectItem>
}
</MudSelect>
}
</MudStack>
@if (!string.IsNullOrWhiteSpace(this.dataLoadingModelsIssue))
{
<MudAlert Severity="Severity.Error" Class="mt-3">
@this.dataLoadingModelsIssue
</MudAlert>
}
</MudField>
<MudAlert Severity="Severity.Warning" Class="mb-3">
@T("The use of this provider has been prohibited by your organization. Please select a different provider.")
</MudAlert>
}
else
{
<MudField FullWidth="true" Label="@T("Model selection")" Variant="Variant.Outlined" Class="mb-3">
<MudText Typo="Typo.body1">
@T("This host uses the model configured at the provider level. No model selection is available.")
</MudText>
</MudField>
@if (this.DataLLMProvider.IsAPIKeyNeeded(this.DataHost))
{
<SecretInputField Secret="@this.dataAPIKey" SecretChanged="@this.OnAPIKeyChanged" Label="@this.APIKeyText" Validation="@this.providerValidation.ValidatingAPIKey"/>
}
@if (this.DataLLMProvider.IsHostnameNeeded())
{
<MudTextField
T="string"
@bind-Text="@this.DataHostname"
Label="@T("Hostname")"
Class="mb-3"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Dns"
AdornmentColor="Color.Info"
Disabled="@this.IsEnterpriseConfiguration"
Validation="@this.providerValidation.ValidatingHostname"
UserAttributes="@SPELLCHECK_ATTRIBUTES"/>
}
@if (this.DataLLMProvider.IsHostNeeded())
{
<MudSelect T="Host" Value="@this.DataHost" ValueChanged="@this.OnHostChanged" Label="@T("Host")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Disabled="@this.IsEnterpriseConfiguration" Validation="@this.providerValidation.ValidatingHost">
@foreach (Host host in Enum.GetValues(typeof(Host)))
{
if (host.IsTranscriptionSupported())
{
<MudSelectItem Value="@host">
@host.Name()
</MudSelectItem>
}
}
</MudSelect>
}
@if (this.DataLLMProvider.IsHFInstanceProviderNeeded())
{
<MudSelect T="HFInferenceProvider" Value="@this.HFInferenceProviderId" ValueChanged="@this.OnHFInferenceProviderChanged" Label="@T("Hugging Face Inference Provider")" Class="mb-3" OpenIcon="@Icons.Material.Filled.Dns" AdornmentColor="Color.Info" Adornment="Adornment.Start" Disabled="@this.IsEnterpriseConfiguration" Validation="@this.providerValidation.ValidatingHFInstanceProviderForTranscription">
@foreach (HFInferenceProvider inferenceProvider in Enum.GetValues(typeof(HFInferenceProvider)))
{
@if (inferenceProvider.SupportsTranscription())
{
<MudSelectItem Value="@inferenceProvider">
@inferenceProvider.ToName()
</MudSelectItem>
}
}
</MudSelect>
<MudJustifiedText Class="mb-3">
@T("Hugging Face transcribes audio through a few of its inference providers only, which is why this list is shorter than the one for chatting.")
</MudJustifiedText>
}
@if (!this.DataLLMProvider.IsTranscriptionModelSelectionHidden(this.DataHost))
{
<MudField FullWidth="true" Label="@T("Model selection")" Variant="Variant.Outlined" Class="mb-3">
<MudStack Row="@true" AlignItems="AlignItems.Center" StretchItems="StretchItems.End">
<MudButton Disabled="@(this.IsEnterpriseConfiguration || !this.DataLLMProvider.CanLoadModels(this.DataHost, this.dataAPIKey))" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="@this.ReloadModels">
@T("Load")
</MudButton>
@if(this.availableModels.Count is 0)
{
<MudText Typo="Typo.body1">
@T("No models loaded or available.")
</MudText>
}
else
{
<MudSelect Disabled="@(this.IsEnterpriseConfiguration || this.IsNoneProvider)" @bind-Value="@this.DataModel" Label="@T("Model")"
OpenIcon="@Icons.Material.Filled.FaceRetouchingNatural"
AdornmentColor="Color.Info" Adornment="Adornment.Start"
Validation="@this.providerValidation.ValidatingModel">
@foreach (var model in this.availableModels)
{
<MudSelectItem Value="@model">
@model
</MudSelectItem>
}
</MudSelect>
}
</MudStack>
@if (!string.IsNullOrWhiteSpace(this.dataLoadingModelsIssue))
{
<MudAlert Severity="Severity.Error" Class="mt-3">
@this.dataLoadingModelsIssue
</MudAlert>
}
</MudField>
}
else
{
<MudField FullWidth="true" Label="@T("Model selection")" Variant="Variant.Outlined" Class="mb-3">
<MudText Typo="Typo.body1">
@T("This host uses the model configured at the provider level. No model selection is available.")
</MudText>
</MudField>
}
@* ReSharper disable once CSharpWarnings::CS8974 *@
<MudTextField
T="string"
@bind-Text="@this.DataName"
Label="@T("Instance Name")"
Class="mb-3"
MaxLength="40"
Counter="40"
Immediate="@true"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Lightbulb"
AdornmentColor="Color.Info"
Disabled="@this.IsEnterpriseConfiguration"
Validation="@this.providerValidation.ValidatingInstanceName"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
/>
}
@* ReSharper disable once CSharpWarnings::CS8974 *@
<MudTextField
T="string"
@bind-Text="@this.DataName"
Label="@T("Instance Name")"
Class="mb-3"
MaxLength="40"
Counter="40"
Immediate="@true"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Lightbulb"
AdornmentColor="Color.Info"
Disabled="@this.IsEnterpriseConfiguration"
Validation="@this.providerValidation.ValidatingInstanceName"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
/>
</MudForm>
<Issues IssuesData="@this.dataIssues"/>
@if (!this.IsProviderProhibited)
{
<Issues IssuesData="@this.dataIssues"/>
}
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Cancel" Variant="Variant.Filled">
@T("Cancel")
</MudButton>
<MudButton OnClick="@this.Store" Variant="Variant.Filled" Color="Color.Primary">
<MudButton OnClick="@this.Store" Variant="Variant.Filled" Color="Color.Primary" Disabled="@this.IsProviderProhibited">
@if(this.IsEditing)
{
@T("Update")
@@ -107,7 +107,16 @@ public partial class TranscriptionProviderDialog : MSGComponentBase, ISecretId
/// The list of used instance names. We need this to check for uniqueness.
/// </summary>
private List<string> UsedInstanceNames { get; set; } = [];
/// <summary>
/// Whether the organization prohibited the selected provider.
/// </summary>
/// <remarks>
/// The provider stays selectable, so that nobody wonders where it went. The dialog then shows
/// the reason instead of the form, and nothing can be stored.
/// </remarks>
private bool IsProviderProhibited => this.SettingsManager.IsProviderProhibited(this.DataLLMProvider);
private bool dataIsValid;
private string[] dataIssues = [];
private string dataAPIKey = string.Empty;
@@ -239,6 +248,9 @@ public partial class TranscriptionProviderDialog : MSGComponentBase, ISecretId
private async Task Store()
{
if (this.IsProviderProhibited)
return;
await this.form.Validate();
this.dataAPIKeyStorageIssue = string.Empty;
@@ -328,6 +340,10 @@ public partial class TranscriptionProviderDialog : MSGComponentBase, ISecretId
private async Task ReloadModels()
{
// Loading the models would contact the provider:
if (this.IsProviderProhibited)
return;
this.dataLoadingModelsIssue = string.Empty;
var currentTranscriptionProviderSettings = this.CreateTranscriptionProviderSettings();
var provider = currentTranscriptionProviderSettings.CreateProvider();
@@ -454,6 +454,34 @@ CONFIG["SETTINGS"] = {}
-- CONFIG["SETTINGS"]["DataApp.AllowUserToAddEmbeddingProvider"] = false
-- CONFIG["SETTINGS"]["DataApp.AllowUserToAddTranscriptionProvider"] = false
-- Prohibit providers, e.g., when your organization must not send any data to them. The list
-- applies to LLM providers, embedding providers, and transcription providers alike. Allowed
-- values are the provider keys of DataConfidence.CustomConfidenceScheme, see below. A name
-- AI Studio does not know is ignored and logged as a warning.
--
-- A prohibition names the operator of an API, not a family of models: prohibiting the API of
-- a model maker keeps AI Studio away from that API, but the models of that maker may remain
-- available through a hub which offers the models of several makers, or on a server of your
-- own. Prohibit these as well if you need to.
--
-- Users can still select a prohibited provider when they add one, but the dialog then only
-- explains that your organization does not allow it. Providers which users set up before stay
-- in their settings, marked as prohibited, and users can delete them. AI Studio no longer
-- offers or contacts them. Once you lift the prohibition, they work again.
--
-- A prohibited provider is always UNTRUSTED, whatever the confidence scheme says. A level which
-- DataConfidence.CustomConfidenceScheme names for it is ignored and logged as a warning, and
-- users cannot change it in the app. Once you lift the prohibition, that level applies again.
--
-- Replaces, does not merge: a configuration with a higher priority replaces the whole list.
-- A configuration which prohibits one more provider has to repeat the providers of the base
-- configuration. Otherwise, these become usable again.
--
-- Available since AI Studio v26.10.1. Older versions ignore this setting. Until all devices
-- run that version, you can in addition use the CUSTOM confidence scheme, set the provider to
-- UNTRUSTED there, and enforce a global minimum confidence level of at least VERY_LOW.
-- CONFIG["SETTINGS"]["DataApp.ProhibitedProviders"] = { "<PROVIDER_KEY>" }
-- Configure the permission to add mailboxes. A mailbox is a data source, not a provider, so
-- DataApp.AllowUserToAddProvider does not apply to it. When set to false, the menu entry for
-- adding a mailbox stays visible but is disabled. Mailboxes the user added before stay; to keep
@@ -1165,6 +1193,9 @@ CONFIG["SETTINGS"] = {}
-- SELF_HOSTED, HELMHOLTZ, GWDG
-- Allowed confidence values are: UNTRUSTED, VERY_LOW, LOW, MODERATE, MEDIUM, HIGH
--
-- A provider which DataApp.ProhibitedProviders names is always UNTRUSTED. Its level in this
-- table is ignored and logged as a warning, see DataApp.ProhibitedProviders above.
--
-- Replaces, does not merge: a configuration with a higher priority replaces the whole
-- table. Every configuration that sets this must therefore list all providers it wants
-- to cover. A partial table is not completed from the configuration below it, and the
@@ -3645,6 +3645,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1543974632"] = "Die Medie
-- {0}, of which approx. {1} from tools
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1614399447"] = "{0}, davon ca. {1} aus Werkzeugen"
-- The use of this provider has been prohibited by your organization. Please select a different provider.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1807387355"] = "Die Verwendung dieses Anbieters wurde von Ihrer Organisation untersagt. Bitte wählen Sie einen anderen Anbieter."
-- Type your input here...
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1849313532"] = "Geben Sie hier Ihre Eingabe ein..."
@@ -4710,6 +4713,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROVIDERSELECTION::T2685487365"] = "Bilde
-- Speech input possible
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROVIDERSELECTION::T3005724142"] = "Spracheingabe möglich"
-- The use of all your LLM providers has been prohibited by your organization. Add a different provider in the app settings.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROVIDERSELECTION::T3506708164"] = "Die Verwendung aller Ihrer LLM-Anbieter wurde von Ihrer Organisation untersagt. Bitte fügen Sie einen anderen Anbieter in den App-Einstellungen hinzu."
-- Uses reasoning (thinking) by default
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROVIDERSELECTION::T3891860124"] = "Nutzt standardmäßig Schlussfolgerungen (Denken)"
@@ -5280,6 +5286,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCONFIDENCE::T45885
-- Not yet configured
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCONFIDENCE::T48051324"] = "Noch nicht konfiguriert"
-- The use of this provider has been prohibited by your organization. Therefore, it is untrusted, and you cannot change its confidence level.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCONFIDENCE::T489079668"] = "Die Verwendung dieses Anbieters wurde von Ihrer Organisation untersagt. Daher gilt er als nicht vertrauenswürdig, und Sie können sein Vertrauensniveau nicht ändern."
-- Do you want to always see how trustworthy your providers are? This way, you stay in control of which provider you send your data to. You can choose a common schema or configure the trust levels for each provider yourself.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCONFIDENCE::T700839804"] = "Möchten Sie immer sehen, wie vertrauenswürdig Ihre Anbieter sind? So behalten Sie die Kontrolle darüber, an welchen Anbieter Sie Ihre Daten senden. Sie können ein gängiges Schema wählen oder die Vertrauensniveaus für jeden Anbieter selbst festlegen."
@@ -5298,6 +5307,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T13870
-- Delete
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1469573738"] = "Löschen"
-- The use of this embedding provider has been prohibited by your organization.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1509600117"] = "Die Verwendung dieses Einbettungsanbieters wurde von Ihrer Organisation untersagt."
-- Embed text
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1644934561"] = "Text einbetten"
@@ -5443,11 +5455,14 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T331371
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3346433704"] = "LLM-Anbieter hinzufügen"
-- This provider is managed by your organization.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3415927576"] = "Dieser Anbieter wird von ihrer Organisation verwaltet."
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3415927576"] = "Dieser Anbieter wird von Ihrer Organisation verwaltet."
-- Actions
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3865031940"] = "Aktionen"
-- The use of this provider has been prohibited by your organization.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T4229691938"] = "Die Verwendung dieses Anbieters wurde von Ihrer Organisation untersagt."
-- Delete LLM Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T4269256234"] = "LLM-Anbieter löschen"
@@ -5511,6 +5526,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T16
-- Uses the provider-configured model
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1760715963"] = "Verwendet das vom Anbieter konfigurierte Modell"
-- The use of this transcription provider has been prohibited by your organization.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T2059509230"] = "Die Verwendung dieses Anbieters für Transkriptionen wurde von Ihrer Organisation untersagt."
-- Add Transcription Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T2066315685"] = "Anbieter für Transkriptionen hinzufügen"
@@ -7635,6 +7653,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1727440780"] = "Ho
-- Load
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1756340745"] = "Laden"
-- The use of this provider has been prohibited by your organization. Please select a different provider.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1807387355"] = "Die Verwendung dieses Anbieters wurde von Ihrer Organisation untersagt. Bitte wählen Sie einen anderen Anbieter."
-- Update
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1847791252"] = "Aktualisieren"
@@ -8160,6 +8181,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1727440780"] = "Hostname"
-- Always on
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1761671861"] = "Immer an"
-- The use of this provider has been prohibited by your organization. Please select a different provider.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1807387355"] = "Die Verwendung dieses Anbieters wurde von Ihrer Organisation untersagt. Bitte wählen Sie einen anderen Anbieter."
-- Reset
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T180921696"] = "Zurücksetzen"
@@ -9939,6 +9963,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1727440780"] =
-- Load
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1756340745"] = "Laden"
-- The use of this provider has been prohibited by your organization. Please select a different provider.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1807387355"] = "Die Verwendung dieses Anbieters wurde von Ihrer Organisation untersagt. Bitte wählen Sie einen anderen Anbieter."
-- Update
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1847791252"] = "Aktualisieren"
@@ -11982,6 +12009,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::AIJOBS::AIJOBSERVICE::T1578005752"] = "Das aus
-- The selected provider is not allowed for this chat.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::AIJOBS::AIJOBSERVICE::T174545104"] = "Der ausgewählte Anbieter ist für diesen Chat nicht zulässig."
-- The use of this provider has been prohibited by your organization. Please select a different provider.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::AIJOBS::AIJOBSERVICE::T1807387355"] = "Die Verwendung dieses Anbieters wurde von Ihrer Organisation untersagt. Bitte wählen Sie einen anderen Anbieter."
-- The AI job failed. The message is: '{0}'
UI_TEXT_CONTENT["AISTUDIO::TOOLS::AIJOBS::AIJOBSERVICE::T237448388"] = "Der KI-Auftrag ist fehlgeschlagen. Die Meldung lautet: „{0}“"
@@ -13827,6 +13857,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T1738200
-- The index of the data source '{0}' is missing. The data source stays out of your chats until its index was built anew. Use the repair action to start that.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T204783307"] = "Der Index der Datenquelle „{0}“ fehlt. Die Datenquelle wird in Ihren Chats nicht verwendet, bis ihr Index neu erstellt wurde. Verwenden Sie die Aktion „Reparieren“, um dies zu starten."
-- The use of the embedding provider of this data source has been prohibited by your organization. Please select a different embedding provider in the settings of the data source.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T2092244746"] = "Die Verwendung des Einbettungsanbieters dieser Datenquelle wurde von Ihrer Organisation untersagt. Bitte wählen Sie in den Einstellungen der Datenquelle einen anderen Einbettungsanbieter."
-- The selected embedding provider is not allowed to index this data source. The data source asks for the confidence level '{0}', while the embedding provider has '{1}'.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T2186533187"] = "Der ausgewählte Einbettungsanbieter darf diese Datenquelle nicht indexieren. Die Datenquelle erfordert das Vertrauensniveau „{0}“, während der Einbettungsanbieter „{1}“ hat."
@@ -14757,6 +14790,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T2222598306"
-- Please select your security policy.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T2250909198"] = "Bitte wählen Sie Ihre Sicherheitsrichtlinie aus."
-- The use of this embedding provider has been prohibited by your organization. Please select a different embedding provider.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T2376506873"] = "Die Verwendung dieses Einbettungsanbieters wurde von Ihrer Organisation untersagt. Bitte wählen Sie einen anderen Einbettungsanbieter."
-- Please acknowledge that you are aware of the cloud transcription implications.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T2818048408"] = "Bitte bestätigen Sie, dass Ihnen die Auswirkungen der Cloud-Transkription bewusst sind."
@@ -3645,6 +3645,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1543974632"] = "The media
-- {0}, of which approx. {1} from tools
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1614399447"] = "{0}, of which approx. {1} from tools"
-- The use of this provider has been prohibited by your organization. Please select a different provider.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1807387355"] = "The use of this provider has been prohibited by your organization. Please select a different provider."
-- Type your input here...
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1849313532"] = "Type your input here..."
@@ -4710,6 +4713,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROVIDERSELECTION::T2685487365"] = "Image
-- Speech input possible
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROVIDERSELECTION::T3005724142"] = "Speech input possible"
-- The use of all your LLM providers has been prohibited by your organization. Add a different provider in the app settings.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROVIDERSELECTION::T3506708164"] = "The use of all your LLM providers has been prohibited by your organization. Add a different provider in the app settings."
-- Uses reasoning (thinking) by default
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROVIDERSELECTION::T3891860124"] = "Uses reasoning (thinking) by default"
@@ -5280,6 +5286,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCONFIDENCE::T45885
-- Not yet configured
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCONFIDENCE::T48051324"] = "Not yet configured"
-- The use of this provider has been prohibited by your organization. Therefore, it is untrusted, and you cannot change its confidence level.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCONFIDENCE::T489079668"] = "The use of this provider has been prohibited by your organization. Therefore, it is untrusted, and you cannot change its confidence level."
-- Do you want to always see how trustworthy your providers are? This way, you stay in control of which provider you send your data to. You can choose a common schema or configure the trust levels for each provider yourself.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCONFIDENCE::T700839804"] = "Do you want to always see how trustworthy your providers are? This way, you stay in control of which provider you send your data to. You can choose a common schema or configure the trust levels for each provider yourself."
@@ -5298,6 +5307,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T13870
-- Delete
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1469573738"] = "Delete"
-- The use of this embedding provider has been prohibited by your organization.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1509600117"] = "The use of this embedding provider has been prohibited by your organization."
-- Embed text
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1644934561"] = "Embed text"
@@ -5448,6 +5460,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T341592
-- Actions
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3865031940"] = "Actions"
-- The use of this provider has been prohibited by your organization.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T4229691938"] = "The use of this provider has been prohibited by your organization."
-- Delete LLM Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T4269256234"] = "Delete LLM Provider"
@@ -5511,6 +5526,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T16
-- Uses the provider-configured model
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1760715963"] = "Uses the provider-configured model"
-- The use of this transcription provider has been prohibited by your organization.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T2059509230"] = "The use of this transcription provider has been prohibited by your organization."
-- Add Transcription Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T2066315685"] = "Add Transcription Provider"
@@ -7635,6 +7653,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1727440780"] = "Ho
-- Load
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1756340745"] = "Load"
-- The use of this provider has been prohibited by your organization. Please select a different provider.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1807387355"] = "The use of this provider has been prohibited by your organization. Please select a different provider."
-- Update
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1847791252"] = "Update"
@@ -8160,6 +8181,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1727440780"] = "Hostname"
-- Always on
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1761671861"] = "Always on"
-- The use of this provider has been prohibited by your organization. Please select a different provider.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1807387355"] = "The use of this provider has been prohibited by your organization. Please select a different provider."
-- Reset
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T180921696"] = "Reset"
@@ -9939,6 +9963,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1727440780"] =
-- Load
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1756340745"] = "Load"
-- The use of this provider has been prohibited by your organization. Please select a different provider.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1807387355"] = "The use of this provider has been prohibited by your organization. Please select a different provider."
-- Update
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1847791252"] = "Update"
@@ -11982,6 +12009,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::AIJOBS::AIJOBSERVICE::T1578005752"] = "The sel
-- The selected provider is not allowed for this chat.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::AIJOBS::AIJOBSERVICE::T174545104"] = "The selected provider is not allowed for this chat."
-- The use of this provider has been prohibited by your organization. Please select a different provider.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::AIJOBS::AIJOBSERVICE::T1807387355"] = "The use of this provider has been prohibited by your organization. Please select a different provider."
-- The AI job failed. The message is: '{0}'
UI_TEXT_CONTENT["AISTUDIO::TOOLS::AIJOBS::AIJOBSERVICE::T237448388"] = "The AI job failed. The message is: '{0}'"
@@ -13827,6 +13857,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T1738200
-- The index of the data source '{0}' is missing. The data source stays out of your chats until its index was built anew. Use the repair action to start that.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T204783307"] = "The index of the data source '{0}' is missing. The data source stays out of your chats until its index was built anew. Use the repair action to start that."
-- The use of the embedding provider of this data source has been prohibited by your organization. Please select a different embedding provider in the settings of the data source.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T2092244746"] = "The use of the embedding provider of this data source has been prohibited by your organization. Please select a different embedding provider in the settings of the data source."
-- The selected embedding provider is not allowed to index this data source. The data source asks for the confidence level '{0}', while the embedding provider has '{1}'.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T2186533187"] = "The selected embedding provider is not allowed to index this data source. The data source asks for the confidence level '{0}', while the embedding provider has '{1}'."
@@ -14757,6 +14790,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T2222598306"
-- Please select your security policy.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T2250909198"] = "Please select your security policy."
-- The use of this embedding provider has been prohibited by your organization. Please select a different embedding provider.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T2376506873"] = "The use of this embedding provider has been prohibited by your organization. Please select a different embedding provider."
-- Please acknowledge that you are aware of the cloud transcription implications.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T2818048408"] = "Please acknowledge that you are aware of the cloud transcription implications."
@@ -341,6 +341,16 @@ public static class LLMProvidersExtensions
private static IProvider CreateProvider(this LLMProviders provider, string instanceName, Host host, string hostname, HFInferenceProvider inferenceProvider, string configuredProviderId = "", string expertProviderApiParameter = "", bool isEnterpriseConfiguration = false, HFEndpointKind hfEndpointKind = HFEndpointKind.CHAT, ProviderCapabilityOverrides? capabilityOverrides = null, string tokenizerPath = "")
{
//
// Every provider instance comes from here, for LLMs, embeddings, and transcriptions alike.
// Whatever a caller forgot to check, no request reaches a provider the organization prohibited:
//
if (IsProhibitedByOrganization(provider))
{
LOGGER.LogWarning($"Refused to create the provider '{instanceName}' of '{provider}': the organization prohibited this provider.");
return new NoProvider();
}
try
{
IProvider providerInstance = provider switch
@@ -382,7 +392,22 @@ public static class LLMProvidersExtensions
return new NoProvider();
}
}
/// <summary>
/// Checks whether the organization prohibited the given provider.
/// </summary>
/// <remarks>
/// The settings come from Program.SERVICE_PROVIDER, because none of the callers of CreateProvider
/// hands them in. Tests which create providers without the services of the app have no settings
/// there, and nothing is prohibited for them.
/// </remarks>
private static bool IsProhibitedByOrganization(LLMProviders provider)
{
// Declared as never null, but it is null until the app has started its services:
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
return Program.SERVICE_PROVIDER?.GetService<SettingsManager>() is { } settingsManager && settingsManager.IsProviderProhibited(provider);
}
public static string GetCreationURL(this LLMProviders provider) => provider switch
{
LLMProviders.OPEN_AI => "https://platform.openai.com/signup",
@@ -1,5 +1,6 @@
using System.Linq.Expressions;
using AIStudio.Provider;
using AIStudio.Tools.PluginSystem;
namespace AIStudio.Settings.DataModel;
@@ -192,6 +193,17 @@ public sealed class DataApp(Expression<Func<Data, DataApp>>? configSelection = n
/// </summary>
public bool AllowUserToAddTranscriptionProvider { get; set; } = ManagedConfiguration.Register(configSelection, n => n.AllowUserToAddTranscriptionProvider, true);
/// <summary>
/// The providers an organization prohibited, for LLMs, embeddings, and transcriptions alike.
/// </summary>
/// <remarks>
/// A prohibition names the operator of an API, not a family of models: prohibiting the API of a
/// model maker leaves its models reachable through a hub or a self-hosted server. Providers the
/// user set up before stay in the settings, so they are back once the prohibition ends, but
/// AI Studio neither offers nor contacts them, see SettingsManager.IsProviderProhibited.
/// </remarks>
public HashSet<LLMProviders> ProhibitedProviders { get; set; } = ManagedConfiguration.Register(configSelection, n => n.ProhibitedProviders, []);
/// <summary>
/// Should the user be allowed to add mailboxes?
/// </summary>
@@ -581,6 +581,14 @@ public static partial class ManagedConfiguration
// Step 3 -- try to parse the string as the target type:
if (EnumNames.TryParse(typeof(TValue), configuredLuaValueText, out var configuredEnum))
set.Add((TValue)configuredEnum);
//
// Dropped, but not quietly: such a set may prohibit something, and a typo would
// otherwise leave it allowed without anybody noticing. A dry run comes before
// the actual one, so only the actual one reports it:
//
else if (!dryRun)
Log.LogWarning($"The configuration plugin '{configPluginId}' names '{configuredLuaValueText}' in the setting '{configMeta.SettingName}', which is no valid value. Ignoring it.");
}
}
@@ -299,7 +299,7 @@ public static partial class ManagedConfiguration
/// <summary>
/// Determines the configuration plugin which currently manages a setting, if any.
/// </summary>
private static Guid GetSettingOwner(ConfigMetaBase configMeta)
internal static Guid GetSettingOwner(ConfigMetaBase configMeta)
{
if (configMeta.IsLocked && configMeta.LockedByConfigPluginId != Guid.Empty)
return configMeta.LockedByConfigPluginId;
@@ -585,7 +585,7 @@ public sealed class SettingsManager
var minimumLevel = this.GetMinimumConfidenceLevel(component);
// When there is only one provider, and it has a confidence level that is high enough, we return it:
if (this.ConfigurationData.Providers.Count == 1 && this.ConfigurationData.Providers[0].UsedLLMProvider.GetConfidence(this).Level >= minimumLevel)
if (this.ConfigurationData.Providers.Count == 1 && this.IsProviderUsable(this.ConfigurationData.Providers[0].UsedLLMProvider, minimumLevel))
return this.ConfigurationData.Providers[0];
// Is there a current provider with a sufficiently high confidence level?
@@ -593,14 +593,14 @@ public sealed class SettingsManager
if (currentProviderId is not null && !string.IsNullOrWhiteSpace(currentProviderId))
{
var currentProviderProbe = this.ConfigurationData.Providers.FirstOrDefault(x => x.Id == currentProviderId);
if (currentProviderProbe is not null && currentProviderProbe.UsedLLMProvider.GetConfidence(this).Level >= minimumLevel)
if (currentProviderProbe is not null && this.IsProviderUsable(currentProviderProbe.UsedLLMProvider, minimumLevel))
currentProvider = currentProviderProbe;
}
// Is there a component-preselected provider with a sufficiently high confidence level?
var preselectedProvider = Provider.NONE;
var preselectedProviderProbe = component.PreselectedProvider(this);
if(preselectedProviderProbe != Provider.NONE && preselectedProviderProbe.UsedLLMProvider.GetConfidence(this).Level >= minimumLevel)
if(preselectedProviderProbe != Provider.NONE && this.IsProviderUsable(preselectedProviderProbe.UsedLLMProvider, minimumLevel))
preselectedProvider = preselectedProviderProbe;
//
@@ -628,7 +628,7 @@ public sealed class SettingsManager
return preselectedProvider;
// When there is an app-wide preselected provider, and it has a confidence level that is high enough, we return it:
return this.ConfigurationData.Providers.FirstOrDefault(x => x.Id == this.ConfigurationData.App.PreselectedProvider && x.UsedLLMProvider.GetConfidence(this).Level >= minimumLevel) ?? Provider.NONE;
return this.ConfigurationData.Providers.FirstOrDefault(x => x.Id == this.ConfigurationData.App.PreselectedProvider && this.IsProviderUsable(x.UsedLLMProvider, minimumLevel)) ?? Provider.NONE;
}
public Provider GetChatProviderForLoadedChat(string? chatProviderId = null)
@@ -665,7 +665,7 @@ public sealed class SettingsManager
bool IsSelectableProvider(Provider provider) =>
provider != Provider.NONE
&& provider.UsedLLMProvider != LLMProviders.NONE
&& provider.UsedLLMProvider.GetConfidence(this).Level >= minimumLevel;
&& this.IsProviderUsable(provider.UsedLLMProvider, minimumLevel);
}
/// <summary>
@@ -684,7 +684,8 @@ public sealed class SettingsManager
/// <para>
/// Whenever a provider is about to be used for an LLM request, do not use this method. Use
/// GetConfidentProviders, GetPreselectedProvider, or GetChatProviderForLoadedChat instead,
/// since they honor the confidence levels.
/// since they honor the confidence levels and leave out the providers the organization
/// prohibited. This method returns those as well, so that the settings can mark them.
/// </para>
/// <para>
/// The returned list is a sorted copy of the provider list, ordered by the used LLM provider and
@@ -706,9 +707,9 @@ public sealed class SettingsManager
/// <remarks>
/// This method resolves a stored provider reference by its id. It applies neither the global
/// minimum confidence level nor any component-specific minimum, so it returns the requested
/// provider even when the user enforces a higher global minimum. Callers that intend to use the
/// returned provider for an LLM request must check it themselves through
/// IsProviderConfident or fall back to GetPreselectedProvider.
/// provider even when the user enforces a higher global minimum or the organization prohibited
/// it. Callers that intend to use the returned provider for an LLM request must check it
/// themselves through IsProviderConfident or fall back to GetPreselectedProvider.
/// </remarks>
/// <param name="providerId">The id of the provider to look up.</param>
/// <returns>The provider, or <see cref="Provider.NONE"/> when no provider with that id exists.</returns>
@@ -741,6 +742,9 @@ public sealed class SettingsManager
/// <summary>
/// Checks whether the given provider satisfies the minimum confidence level of the given component.
/// </summary>
/// <remarks>
/// A provider the organization prohibited never does, see IsProviderUsable.
/// </remarks>
/// <param name="provider">The provider to check.</param>
/// <param name="component">The component for which the provider gets checked.</param>
/// <param name="explicitMinimum">An explicit minimum level, which is applied when it is higher than the component's minimum.</param>
@@ -750,12 +754,51 @@ public sealed class SettingsManager
if (provider.UsedLLMProvider is LLMProviders.NONE)
return false;
return provider.UsedLLMProvider.GetConfidence(this).Level >= this.GetEffectiveMinimumConfidenceLevel(component, explicitMinimum);
return this.IsProviderUsable(provider.UsedLLMProvider, this.GetEffectiveMinimumConfidenceLevel(component, explicitMinimum));
}
/// <summary>
/// Checks whether the organization prohibited the given provider.
/// </summary>
/// <remarks>
/// A prohibition is a rule of its own, next to confidence and data-source security: no confidence
/// level and no trust by configuration makes a prohibited provider usable. It also makes the provider
/// untrusted, see GetConfiguredConfidenceLevel, but not the other way around. It names the operator
/// of an API, so a hub or a self-hosted server which serves the same models stays allowed.
/// </remarks>
/// <param name="provider">The provider to check, for LLMs, embeddings, and transcriptions alike.</param>
/// <returns>True, when the organization prohibited the provider. NONE is never prohibited.</returns>
public bool IsProviderProhibited(LLMProviders provider) => provider is not LLMProviders.NONE && this.ConfigurationData.App.ProhibitedProviders.Contains(provider);
/// <summary>
/// Checks whether the given provider may be used where the given minimum confidence level applies.
/// </summary>
/// <remarks>
/// The one check for every place which picks a provider: the organization did not prohibit it, and
/// its confidence level reaches the minimum. A prohibited provider is untrusted, see
/// GetConfiguredConfidenceLevel, but comparing the confidence level alone would still let it through
/// wherever no minimum is enforced, since even untrusted reaches the minimum NONE.
/// </remarks>
/// <param name="provider">The provider to check, for LLMs, embeddings, and transcriptions alike.</param>
/// <param name="minimumLevel">The minimum confidence level which applies.</param>
/// <returns>True, when the provider may be used, false otherwise.</returns>
public bool IsProviderUsable(LLMProviders provider, ConfidenceLevel minimumLevel) => !this.IsProviderProhibited(provider) && provider.GetConfidence(this).Level >= minimumLevel;
/// <summary>
/// Returns the prohibited providers which have a level of their own in the custom confidence scheme.
/// </summary>
/// <remarks>
/// The prohibition outranks these levels, see GetConfiguredConfidenceLevel.
/// </remarks>
/// <returns>The prohibited providers with a custom confidence level, in no particular order.</returns>
public IEnumerable<LLMProviders> GetProhibitedProvidersWithCustomConfidenceLevel() => this.ConfigurationData.Confidence.CustomConfidenceScheme.Keys.Where(this.IsProviderProhibited);
/// <summary>
/// Returns all providers that satisfy the minimum confidence level of the given component.
/// </summary>
/// <remarks>
/// The providers the organization prohibited are left out, see IsProviderUsable.
/// </remarks>
/// <param name="component">The component for which the providers get filtered.</param>
/// <param name="explicitMinimum">An explicit minimum level, which is applied when it is higher than the component's minimum.</param>
/// <returns>All providers the component may use, in the same order as GetAllProviders.</returns>
@@ -763,7 +806,7 @@ public sealed class SettingsManager
{
var minimumLevel = this.GetEffectiveMinimumConfidenceLevel(component, explicitMinimum);
foreach (var provider in this.GetAllProviders())
if (provider.UsedLLMProvider is not LLMProviders.NONE && provider.UsedLLMProvider.GetConfidence(this).Level >= minimumLevel)
if (provider.UsedLLMProvider is not LLMProviders.NONE && this.IsProviderUsable(provider.UsedLLMProvider, minimumLevel))
yield return provider;
}
@@ -1153,11 +1196,25 @@ public sealed class SettingsManager
this.ConfigurationData.Tools.MinimumProviderConfidenceByToolId[settingsIds[0]] = confidenceLevel.ToString();
}
/// <summary>
/// Returns the confidence level of the given provider.
/// </summary>
/// <remarks>
/// A prohibition by the organization comes first: a prohibited provider is always untrusted, no matter
/// what the confidence scheme says, and no matter which level the user or a configuration plugin chose
/// for it in the custom scheme. Those levels stay stored, so they apply again once the prohibition ends.
/// After that, the confidence scheme decides, and in the custom scheme, the level chosen for the provider.
/// </remarks>
/// <param name="llmProvider">The provider, for LLMs, embeddings, and transcriptions alike.</param>
/// <returns>The confidence level of the provider.</returns>
public ConfidenceLevel GetConfiguredConfidenceLevel(LLMProviders llmProvider)
{
if(llmProvider is LLMProviders.NONE)
return ConfidenceLevel.NONE;
if (this.IsProviderProhibited(llmProvider))
return ConfidenceLevel.UNTRUSTED;
switch (this.ConfigurationData.Confidence.ConfidenceScheme)
{
case ConfidenceSchemes.TRUST_ALL:
@@ -255,6 +255,14 @@ public sealed class AIJobService(SettingsManager settingsManager, MessageBus mes
var provider = request.ProviderSettings.CreateProvider();
var chatThread = request.ChatThread;
// Checked on its own, so that the user learns what to change:
if (settingsManager.IsProviderProhibited(request.ProviderSettings.UsedLLMProvider))
{
logger.LogError("The provider of chat '{ChatId}' is prohibited by the organization. Skipping the AI process.", chatThread.ChatId);
await this.CompleteChatGenerationAsync(state, AIJobStatus.FAILED, TB("The use of this provider has been prohibited by your organization. Please select a different provider."));
return;
}
if (!chatThread.IsLLMProviderAllowed(provider))
{
logger.LogError("The provider is not allowed for chat '{ChatId}' due to data security reasons. Skipping the AI process.", chatThread.ChatId);
@@ -330,6 +330,9 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
// Config: hide some assistants?
ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.HiddenAssistants, this.Id, settingsTable, dryRun);
// Config: prohibit some providers?
ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.ProhibitedProviders, this.Id, settingsTable, dryRun);
// Config: global voice recording shortcut
ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.ShortcutVoiceRecording, this.Id, settingsTable, dryRun);
@@ -333,6 +333,10 @@ public static partial class PluginFactory
if(ManagedConfiguration.CleanupLeftOverManagedConfigurations(AVAILABLE_PLUGINS, deployedEnterpriseConfigPluginIds))
wasConfigurationChanged = true;
// Every configuration plugin has run and the left-overs are gone, so the result no longer
// depends on the order in which the plugins ran:
LogConfidenceLevelsOutrankedByProhibitions();
//
// The enterprise approvals of all configuration plugins add up. Now that every plugin has
// contributed and the clean-up above has dropped the removed ones, we rebuild the effective
@@ -365,6 +369,28 @@ public static partial class PluginFactory
FIRST_LOAD_COMPLETED.TrySetResult();
}
/// <summary>
/// Logs the confidence levels which a configuration plugin chose for prohibited providers.
/// </summary>
/// <remarks>
/// A prohibited provider is untrusted, whatever level the custom confidence scheme names for it, see
/// SettingsManager.GetConfiguredConfidenceLevel. Such a level contradicts the prohibition, so we tell
/// the organization that it is ignored. Levels the user chose are ignored the same way, without a log.
/// </remarks>
private static void LogConfidenceLevelsOutrankedByProhibitions()
{
if (!ManagedConfiguration.TryGet(x => x.Confidence, x => x.CustomConfidenceScheme, out var configMeta))
return;
var owningConfigPluginId = ManagedConfiguration.GetSettingOwner(configMeta);
if (owningConfigPluginId == Guid.Empty)
return;
var customConfidenceScheme = SettingsManagerAccess.ConfigurationData.Confidence.CustomConfidenceScheme;
foreach (var llmProvider in SettingsManagerAccess.GetProhibitedProvidersWithCustomConfidenceLevel())
LOG.LogWarning($"The configuration plugin '{owningConfigPluginId}' sets the confidence level '{customConfidenceScheme[llmProvider]}' for the provider '{llmProvider}' in the custom confidence scheme. Ignoring it: your organization prohibited this provider, so it is untrusted.");
}
/// <summary>
/// Determines the IDs of all configuration plugins which an organization deployed on this machine.
/// </summary>
@@ -7,12 +7,30 @@ namespace AIStudio.Tools.Services;
internal static class DataSourceEmbeddingProviders
{
/// <summary>
/// Finds the embedding provider of a data source, when AI Studio may use it.
/// </summary>
/// <remarks>
/// A provider the organization prohibited counts as missing: nothing may be embedded with it,
/// neither the documents of the data source nor a query against its index.
/// </remarks>
public static bool TryResolve(SettingsManager settingsManager, IDataSourceBase dataSource, [NotNullWhen(true)] out EmbeddingProvider? embeddingProvider)
{
embeddingProvider = settingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(provider =>
dataSource is IIndexedDataSource indexedDataSource &&
provider.Id.Equals(indexedDataSource.EmbeddingId, StringComparison.OrdinalIgnoreCase));
return embeddingProvider != default && embeddingProvider.UsedLLMProvider is not LLMProviders.NONE;
embeddingProvider = Find(settingsManager, dataSource);
return embeddingProvider is { UsedLLMProvider: not LLMProviders.NONE } && !settingsManager.IsProviderProhibited(embeddingProvider.UsedLLMProvider);
}
}
/// <summary>
/// Checks whether the organization prohibited the embedding provider of a data source.
/// </summary>
/// <remarks>
/// TryResolve fails for such a provider as well, but without the reason. The status of a data
/// source needs the reason to tell the user what to change.
/// </remarks>
public static bool IsProhibited(SettingsManager settingsManager, IDataSourceBase dataSource)
=> Find(settingsManager, dataSource) is { } embeddingProvider && settingsManager.IsProviderProhibited(embeddingProvider.UsedLLMProvider);
private static EmbeddingProvider? Find(SettingsManager settingsManager, IDataSourceBase dataSource) => settingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(provider =>
dataSource is IIndexedDataSource indexedDataSource &&
provider.Id.Equals(indexedDataSource.EmbeddingId, StringComparison.OrdinalIgnoreCase));
}
@@ -801,7 +801,9 @@ public sealed partial class DataSourceEmbeddingService(SettingsManager settingsM
if (!this.TryResolveEmbeddingProvider(dataSource, out var embeddingProvider))
{
token.ThrowIfCancellationRequested();
this.UpsertStatus(this.GetFallbackStatus(dataSource, TB("The selected embedding provider is not available. Please check it in the settings.")));
this.UpsertStatus(this.GetFallbackStatus(dataSource, DataSourceEmbeddingProviders.IsProhibited(settingsManager, dataSource)
? TB("The use of the embedding provider of this data source has been prohibited by your organization. Please select a different embedding provider in the settings of the data source.")
: TB("The selected embedding provider is not available. Please check it in the settings.")));
return null;
}
@@ -754,10 +754,11 @@ public sealed class MediaTranscriptionService(
/// <summary>Resolves the configured provider after confidence validation.</summary>
/// <remarks>
/// Checks the global minimum confidence only. A caller with rules of its own, such as the
/// confidence level of a data source, checks the provider against them as well.
/// Checks the global minimum confidence and whether the organization prohibited the provider. A
/// caller with rules of its own, such as the confidence level of a data source, checks the
/// provider against them as well.
/// </remarks>
/// <returns>The configured provider, or null when none is configured or it falls short of the global minimum.</returns>
/// <returns>The configured provider, or null when none is configured, it falls short of the global minimum, or it is prohibited.</returns>
public TranscriptionProvider? GetUsableProvider()
{
var providerId = settingsManager.ConfigurationData.App.UseTranscriptionProvider;
@@ -769,7 +770,7 @@ public sealed class MediaTranscriptionService(
return null;
var minimumLevel = settingsManager.GetMinimumConfidenceLevel(Components.NONE);
return providerSettings.UsedLLMProvider.GetConfidence(settingsManager).Level >= minimumLevel
return settingsManager.IsProviderUsable(providerSettings.UsedLLMProvider, minimumLevel)
? providerSettings
: null;
}
@@ -19,14 +19,19 @@ public static class PendingUserAPIKeys
/// <returns>One group per configuration plugin with at least one waiting provider.</returns>
public static async Task<IReadOnlyList<PendingUserAPIKeyGroup>> FindAsync(SettingsManager settingsManager, RustService rustService)
{
//
// A provider the organization prohibited is left out, even when a configuration of the same
// organization asks for its key: the key could be used for nothing, and checking it would
// contact the provider.
//
var candidates = new List<(Guid ConfigPluginId, PendingUserAPIKeyTarget Target)>();
foreach (var provider in settingsManager.GetAllProviders().Where(provider => IsPrompting(provider)))
foreach (var provider in settingsManager.GetAllProviders().Where(provider => IsPrompting(provider) && !settingsManager.IsProviderProhibited(provider.UsedLLMProvider)))
candidates.Add((provider.EnterpriseConfigurationPluginId, new PendingUserAPIKeyTarget(provider.InstanceName, provider, SecretStoreType.LLM_PROVIDER, () => provider.CreateProvider())));
foreach (var provider in settingsManager.GetAllEmbeddingProviders().Where(provider => IsPrompting(provider)))
foreach (var provider in settingsManager.GetAllEmbeddingProviders().Where(provider => IsPrompting(provider) && !settingsManager.IsProviderProhibited(provider.UsedLLMProvider)))
candidates.Add((provider.EnterpriseConfigurationPluginId, new PendingUserAPIKeyTarget(provider.Name, provider, SecretStoreType.EMBEDDING_PROVIDER, () => provider.CreateProvider())));
foreach (var provider in settingsManager.GetAllTranscriptionProviders().Where(provider => IsPrompting(provider)))
foreach (var provider in settingsManager.GetAllTranscriptionProviders().Where(provider => IsPrompting(provider) && !settingsManager.IsProviderProhibited(provider.UsedLLMProvider)))
candidates.Add((provider.EnterpriseConfigurationPluginId, new PendingUserAPIKeyTarget(provider.Name, provider, SecretStoreType.TRANSCRIPTION_PROVIDER, () => provider.CreateProvider())));
if (candidates.Count == 0)
@@ -188,9 +188,13 @@ public sealed class DataSourceValidation
// An id which matches no provider belongs to one which was deleted since. Saved like this, the
// data source keeps pointing at a provider nobody can reach, and nothing of it is embedded:
//
if(this.GetSelectedEmbeddingProvider() is null)
if(this.GetSelectedEmbeddingProvider() is not { } selectedEmbedding)
return TB("The embedding provider of this data source no longer exists. Please select another one.");
// Saved like this, nothing of the data source would ever be embedded:
if(this.GetSettingsManager() is { } settingsManager && settingsManager.IsProviderProhibited(selectedEmbedding.UsedLLMProvider))
return TB("The use of this embedding provider has been prohibited by your organization. Please select a different embedding provider.");
return this.ValidateSelectedEmbeddingProviderAccess();
}
@@ -24,6 +24,7 @@
- Added a choice of how AI Studio encrypts API keys, tokens, and passwords when you export providers, data sources, or tool settings for your organization. The new protected format needs AI Studio v26.10.1 or newer on every computer that reads it, and your encryption secret stays the same. If you used AI Studio before, your exports keep the previous format until you switch, so your organization can update all computers first. You find the choice in the app settings, under Enterprise Administration.
- Added organization-wide management for the format in which exports encrypt API keys, tokens, and passwords. IT departments can set it for all their administrators, either fixed or as a default which users may change.
- Added a way for IT departments to ask you for your personal API key on the start page. When the configuration of your organization brings providers that need your own key, the start page explains how to get it, checks the key with the provider, and stores it for all of these providers at once. Thanks, Dominic Neuburg (`donework`), for this contribution.
- Added a way for IT departments to prohibit providers. When you add a provider, you can still select a prohibited one, but AI Studio then tells you that your organization does not allow it. Providers you set up before stay in your settings, but AI Studio no longer uses them.
- Added providers, profiles, and workspaces to your chat templates. A template can now decide which provider, and with it which model, and which profile a chat starts with, and keep its chats in a workspace of its own. A template for your mails, for example, can start every chat with a provider that meets the confidence level your mailbox requires. You can still pick another provider or profile in the chat.
- Added a way for IT departments to roll out chat templates that bring their own provider, profile, and workspace. Set the template up in the app, then export it as ready-made Lua code for your configuration plugin.
- Added settings to the Assistant Builder. There you choose the provider, and with it the model, that the Assistant Builder starts with, the language in which the assistants you build answer, and the minimum confidence level a provider needs. Thanks, Nils Kruthoff (`nilskruthoff`), for this contribution.
@@ -0,0 +1,357 @@
using AIStudio.Chat;
using AIStudio.Provider;
using AIStudio.Settings;
using AIStudio.Settings.DataModel;
using AIStudio.Tools.Services;
using AIStudio.Tools.Validation;
using Lua;
using Lua.Standard;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
namespace AIStudio.Tests.Settings;
/// <summary>
/// Checks the providers an organization prohibited through a configuration plugin.
/// </summary>
/// <remarks>
/// The settings are reached through Program.SERVICE_PROVIDER, as in the app, because a managed
/// setting writes its value and its lock there. Names are written out in full where a short one
/// would find a namespace of this assembly first, see PreselectedProviderTests.
/// </remarks>
[TestFixture]
public sealed class ProhibitedProvidersTests
{
private static readonly Guid PLUGIN_ID = Guid.Parse("7b0f9d4e-6a1c-4c55-9a33-0f6d2c9e8b41");
private const string ALLOWED_PROVIDER_ID = "11111111-1111-1111-1111-111111111111";
private const string PROHIBITED_PROVIDER_ID = "22222222-2222-2222-2222-222222222222";
private SettingsManager settingsManager = null!;
private ServiceProvider serviceProvider = null!;
private IServiceProvider previousServiceProvider = null!;
[SetUp]
public void CreateSettings()
{
// Creating the settings data registers every managed setting:
this.settingsManager = new SettingsManager(NullLogger<SettingsManager>.Instance, null!);
this.previousServiceProvider = Program.SERVICE_PROVIDER;
this.serviceProvider = new ServiceCollection().AddSingleton(this.settingsManager).BuildServiceProvider();
Program.SERVICE_PROVIDER = this.serviceProvider;
}
[TearDown]
public void RestoreApplicationState()
{
// The managed state of a setting outlives this fixture, so we release what the plugin took over:
ManagedConfiguration.CleanupLeftOverManagedConfigurations([], new HashSet<Guid>());
Program.SERVICE_PROVIDER = this.previousServiceProvider;
this.serviceProvider.Dispose();
}
[Test]
public async Task APluginProhibitsTheProvidersItNames()
{
await ProcessAsync("""{ "DEEP_SEEK", "mistral" }""");
Assert.Multiple(() =>
{
Assert.That(this.settingsManager.ConfigurationData.App.ProhibitedProviders, Is.EquivalentTo(new[] { LLMProviders.DEEP_SEEK, LLMProviders.MISTRAL }), "Names count whatever their case, as everywhere in a configuration.");
Assert.That(this.settingsManager.IsProviderProhibited(LLMProviders.DEEP_SEEK), Is.True);
Assert.That(this.settingsManager.IsProviderProhibited(LLMProviders.OPEN_AI), Is.False, "A provider the plugin does not name stays allowed.");
});
}
/// <summary>
/// Checks that a name which is no provider prohibits nothing and leaves the rest of the list intact.
/// </summary>
/// <remarks>
/// "DEEPSEEK" is the typo an administrator would make. It must not take the valid names down with it,
/// and it must not turn into some other provider either.
/// </remarks>
[Test]
public async Task AnUnknownNameProhibitsNothing()
{
await ProcessAsync("""{ "DEEPSEEK", "ANTHROPIC", "11" }""");
Assert.That(this.settingsManager.ConfigurationData.App.ProhibitedProviders, Is.EquivalentTo(new[] { LLMProviders.ANTHROPIC }), "Only the valid name may count, and a number is no name.");
}
[Test]
public async Task TheProhibitionIsLocked()
{
await ProcessAsync("""{ "DEEP_SEEK" }""");
var isManaged = ManagedConfiguration.TryGet(x => x.App, x => x.ProhibitedProviders, out var configMeta);
Assert.Multiple(() =>
{
Assert.That(isManaged, Is.True);
Assert.That(configMeta.IsLocked, Is.True, "Users must not lift a prohibition of their organization.");
Assert.That(configMeta.LockedByConfigPluginId, Is.EqualTo(PLUGIN_ID));
});
}
[Test]
public async Task RemovingThePluginLiftsTheProhibition()
{
await ProcessAsync("""{ "DEEP_SEEK" }""");
ManagedConfiguration.CleanupLeftOverManagedConfigurations([], new HashSet<Guid>());
Assert.That(this.settingsManager.IsProviderProhibited(LLMProviders.DEEP_SEEK), Is.False, "Without its plugin, the prohibition has to go, and the providers set up before are usable again.");
}
/// <summary>
/// Checks that NONE is never prohibited.
/// </summary>
/// <remarks>
/// NONE stands for "nothing selected". Counting it as prohibited would show the notice of the
/// organization in every empty provider dialog.
/// </remarks>
[Test]
public async Task NoneIsNeverProhibited()
{
await ProcessAsync("""{ "NONE" }""");
Assert.That(this.settingsManager.IsProviderProhibited(LLMProviders.NONE), Is.False);
}
[Test]
public void SelectionsLeaveOutProhibitedProviders()
{
this.AddProvider(ALLOWED_PROVIDER_ID, LLMProviders.OPEN_AI);
this.AddProvider(PROHIBITED_PROVIDER_ID, LLMProviders.DEEP_SEEK);
this.settingsManager.ConfigurationData.App.ProhibitedProviders.Add(LLMProviders.DEEP_SEEK);
var selectable = this.settingsManager.GetConfidentProviders(AIStudio.Tools.Components.CHAT).Select(x => x.Id).ToList();
Assert.Multiple(() =>
{
Assert.That(selectable, Is.EqualTo(new[] { ALLOWED_PROVIDER_ID }), "A prohibited provider must not be offered, whatever its confidence level.");
Assert.That(this.settingsManager.GetAllProviders(), Has.Count.EqualTo(2), "The settings still list it, so that the user can see and delete it.");
});
}
[Test]
public void ThePreselectionSkipsProhibitedProviders()
{
this.AddProvider(ALLOWED_PROVIDER_ID, LLMProviders.OPEN_AI);
this.AddProvider(PROHIBITED_PROVIDER_ID, LLMProviders.DEEP_SEEK);
this.settingsManager.ConfigurationData.App.PreselectedProvider = PROHIBITED_PROVIDER_ID;
this.settingsManager.ConfigurationData.App.ProhibitedProviders.Add(LLMProviders.DEEP_SEEK);
Assert.Multiple(() =>
{
Assert.That(this.settingsManager.GetPreselectedProvider(AIStudio.Tools.Components.CHAT, PROHIBITED_PROVIDER_ID), Is.EqualTo(AIStudio.Settings.Provider.NONE), "Neither the current provider nor the app-wide default may hand over a prohibited provider.");
Assert.That(this.settingsManager.GetChatProviderForLoadedChat(PROHIBITED_PROVIDER_ID).Id, Is.Not.EqualTo(PROHIBITED_PROVIDER_ID), "A chat stored with a prohibited provider has to continue with another one.");
});
}
/// <summary>
/// Checks that a single prohibited provider is not handed over by the shortcut for a single provider.
/// </summary>
/// <remarks>
/// With exactly one configured provider, GetPreselectedProvider returns it without looking at any
/// preselection. That shortcut must check the prohibition as well.
/// </remarks>
[Test]
public void TheOnlyProviderIsNotHandedOverWhenProhibited()
{
this.AddProvider(PROHIBITED_PROVIDER_ID, LLMProviders.DEEP_SEEK);
this.settingsManager.ConfigurationData.App.ProhibitedProviders.Add(LLMProviders.DEEP_SEEK);
Assert.That(this.settingsManager.GetPreselectedProvider(AIStudio.Tools.Components.CHAT), Is.EqualTo(AIStudio.Settings.Provider.NONE));
}
[Test]
public void AProhibitedProviderIsAllowedInNoChat()
{
this.AddProvider(ALLOWED_PROVIDER_ID, LLMProviders.OPEN_AI);
this.AddProvider(PROHIBITED_PROVIDER_ID, LLMProviders.DEEP_SEEK);
this.settingsManager.ConfigurationData.App.ProhibitedProviders.Add(LLMProviders.DEEP_SEEK);
var allowed = this.settingsManager.GetProviderById(ALLOWED_PROVIDER_ID);
var prohibited = this.settingsManager.GetProviderById(PROHIBITED_PROVIDER_ID);
ChatThread? newChat = null;
Assert.Multiple(() =>
{
Assert.That(newChat.IsLLMProviderAllowed(prohibited), Is.False, "Even a new chat, which has no requirements yet, must not use a prohibited provider.");
Assert.That(new ChatThread().IsLLMProviderAllowed(prohibited), Is.False);
Assert.That(newChat.IsLLMProviderAllowed(allowed), Is.True);
});
}
[Test]
public void ADataSourceEmbedsNothingWithAProhibitedProvider()
{
var embeddingProvider = this.AddEmbeddingProvider(LLMProviders.MISTRAL);
var dataSource = new DataSourceLocalDirectory { EmbeddingId = embeddingProvider.Id };
Assert.That(DataSourceEmbeddingProviders.TryResolve(this.settingsManager, dataSource, out _), Is.True, "Before the prohibition, the provider is there to use.");
this.settingsManager.ConfigurationData.App.ProhibitedProviders.Add(LLMProviders.MISTRAL);
Assert.Multiple(() =>
{
Assert.That(DataSourceEmbeddingProviders.TryResolve(this.settingsManager, dataSource, out _), Is.False, "Indexing and searching must not embed anything with a prohibited provider.");
Assert.That(DataSourceEmbeddingProviders.IsProhibited(this.settingsManager, dataSource), Is.True, "The status of the data source has to name the prohibition as the reason.");
});
}
[Test]
public void TheDataSourceDialogsRefuseAProhibitedEmbeddingProvider()
{
var embeddingProvider = this.AddEmbeddingProvider(LLMProviders.MISTRAL);
var validation = new DataSourceValidation
{
GetSelectedEmbeddingProvider = () => embeddingProvider,
GetSettingsManager = () => this.settingsManager,
};
Assert.That(validation.ValidateEmbeddingProviderAccess(embeddingProvider.Id), Is.Null, "Before the prohibition, the provider may be selected.");
this.settingsManager.ConfigurationData.App.ProhibitedProviders.Add(LLMProviders.MISTRAL);
Assert.That(validation.ValidateEmbeddingProviderAccess(embeddingProvider.Id), Is.Not.Null, "A data source saved with a prohibited provider would never be embedded.");
}
[Test]
public void AProhibitedTranscriptionProviderTranscribesNothing()
{
var transcriptionProvider = new TranscriptionProvider { Id = Guid.NewGuid().ToString(), UsedLLMProvider = LLMProviders.OPEN_AI };
this.settingsManager.ConfigurationData.TranscriptionProviders.Add(transcriptionProvider);
this.settingsManager.ConfigurationData.App.UseTranscriptionProvider = transcriptionProvider.Id;
// GetUsableProvider reads only the settings, so the other services stay out of this test:
using var transcriptionService = new MediaTranscriptionService(null!, this.settingsManager, null!, [], NullLogger<MediaTranscriptionService>.Instance);
Assert.That(transcriptionService.GetUsableProvider(), Is.Not.Null, "Before the prohibition, the provider is there to use.");
this.settingsManager.ConfigurationData.App.ProhibitedProviders.Add(LLMProviders.OPEN_AI);
Assert.That(transcriptionService.GetUsableProvider(), Is.Null, "Dictation and the transcription of recordings must not reach a prohibited provider.");
}
/// <summary>
/// Checks that no instance of a prohibited provider is created, whatever the caller checked before.
/// </summary>
/// <remarks>
/// Every request to a provider needs an instance from CreateProvider. Refusing it there keeps a
/// prohibited provider out of reach of every path, also of one somebody adds later without a check.
/// </remarks>
[Test]
public void NoInstanceOfAProhibitedProviderIsCreated()
{
this.AddProvider(ALLOWED_PROVIDER_ID, LLMProviders.OPEN_AI);
this.AddProvider(PROHIBITED_PROVIDER_ID, LLMProviders.DEEP_SEEK);
var embeddingProvider = this.AddEmbeddingProvider(LLMProviders.DEEP_SEEK);
var transcriptionProvider = new TranscriptionProvider { Id = Guid.NewGuid().ToString(), UsedLLMProvider = LLMProviders.DEEP_SEEK };
this.settingsManager.ConfigurationData.App.ProhibitedProviders.Add(LLMProviders.DEEP_SEEK);
Assert.Multiple(() =>
{
Assert.That(this.settingsManager.GetProviderById(PROHIBITED_PROVIDER_ID).CreateProvider(), Is.TypeOf<NoProvider>());
Assert.That(embeddingProvider.CreateProvider(), Is.TypeOf<NoProvider>());
Assert.That(transcriptionProvider.CreateProvider(), Is.TypeOf<NoProvider>());
Assert.That(this.settingsManager.GetProviderById(ALLOWED_PROVIDER_ID).CreateProvider(), Is.Not.TypeOf<NoProvider>(), "Providers the organization did not prohibit are created as before.");
});
}
[Test]
public void AProhibitedProviderIsUntrustedInEveryScheme()
{
this.settingsManager.ConfigurationData.App.ProhibitedProviders.Add(LLMProviders.DEEP_SEEK);
Assert.Multiple(() =>
{
foreach (var scheme in Enum.GetValues<AIStudio.Tools.ConfidenceSchemes>())
{
this.settingsManager.ConfigurationData.Confidence.ConfidenceScheme = scheme;
Assert.That(LLMProviders.DEEP_SEEK.GetConfidence(this.settingsManager).Level, Is.EqualTo(ConfidenceLevel.UNTRUSTED), $"The scheme {scheme} must not trust a prohibited provider.");
Assert.That(LLMProviders.OPEN_AI.GetConfidence(this.settingsManager).Level, Is.Not.EqualTo(ConfidenceLevel.UNTRUSTED), $"Under the scheme {scheme}, a provider the organization did not prohibit keeps its level.");
}
});
}
/// <summary>
/// Checks that a prohibition outranks a level chosen in the custom scheme, without replacing it.
/// </summary>
/// <remarks>
/// The level may come from the user or from a configuration plugin. Either way, it has to apply again
/// once the organization lifts the prohibition.
/// </remarks>
[Test]
public void TheProhibitionOutranksACustomLevelWithoutReplacingIt()
{
var confidence = this.settingsManager.ConfigurationData.Confidence;
confidence.ConfidenceScheme = AIStudio.Tools.ConfidenceSchemes.CUSTOM;
confidence.CustomConfidenceScheme[LLMProviders.DEEP_SEEK] = ConfidenceLevel.HIGH;
this.settingsManager.ConfigurationData.App.ProhibitedProviders.Add(LLMProviders.DEEP_SEEK);
Assert.Multiple(() =>
{
Assert.That(this.settingsManager.GetConfiguredConfidenceLevel(LLMProviders.DEEP_SEEK), Is.EqualTo(ConfidenceLevel.UNTRUSTED), "The prohibition comes before the chosen level.");
Assert.That(confidence.CustomConfidenceScheme[LLMProviders.DEEP_SEEK], Is.EqualTo(ConfidenceLevel.HIGH), "The chosen level stays stored.");
});
this.settingsManager.ConfigurationData.App.ProhibitedProviders.Remove(LLMProviders.DEEP_SEEK);
Assert.That(this.settingsManager.GetConfiguredConfidenceLevel(LLMProviders.DEEP_SEEK), Is.EqualTo(ConfidenceLevel.HIGH), "Without the prohibition, the chosen level applies again.");
}
[Test]
public void TheCustomLevelsOfProhibitedProvidersAreFound()
{
var customConfidenceScheme = this.settingsManager.ConfigurationData.Confidence.CustomConfidenceScheme;
customConfidenceScheme[LLMProviders.DEEP_SEEK] = ConfidenceLevel.HIGH;
customConfidenceScheme[LLMProviders.OPEN_AI] = ConfidenceLevel.MEDIUM;
this.settingsManager.ConfigurationData.App.ProhibitedProviders.UnionWith([LLMProviders.DEEP_SEEK, LLMProviders.MISTRAL]);
Assert.That(this.settingsManager.GetProhibitedProvidersWithCustomConfidenceLevel(), Is.EquivalentTo(new[] { LLMProviders.DEEP_SEEK }), "Only a prohibited provider with a level of its own has a level which the prohibition outranks.");
}
/// <summary>
/// Adds an embedding provider to the settings.
/// </summary>
private EmbeddingProvider AddEmbeddingProvider(LLMProviders llmProvider)
{
var embeddingProviders = this.settingsManager.ConfigurationData.EmbeddingProviders;
var embeddingProvider = new EmbeddingProvider((uint)embeddingProviders.Count + 1, Guid.NewGuid().ToString(), $"Embedding {embeddingProviders.Count + 1}", llmProvider, new("test-embedding", null));
embeddingProviders.Add(embeddingProvider);
return embeddingProvider;
}
/// <summary>
/// Adds a provider to the settings.
/// </summary>
private void AddProvider(string id, LLMProviders llmProvider)
{
var providers = this.settingsManager.ConfigurationData.Providers;
providers.Add(new((uint)providers.Count + 1, id, $"Instance {providers.Count + 1}", llmProvider, new("test-model", null)));
}
/// <summary>
/// Lets a configuration plugin set the prohibited providers.
/// </summary>
/// <param name="luaList">The Lua table of provider names.</param>
private static async Task ProcessAsync(string luaList)
{
var state = LuaState.Create();
state.OpenBasicLibrary();
state.OpenTableLibrary();
await state.DoStringAsync($$"""
SETTINGS = {}
SETTINGS["DataApp.ProhibitedProviders"] = {{luaList}}
""");
if (!state.Environment["SETTINGS"].TryRead<LuaTable>(out var settingsTable))
throw new InvalidOperationException("The settings of this test are not a Lua table.");
var processed = ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.ProhibitedProviders, PLUGIN_ID, settingsTable, false);
Assert.That(processed, Is.True, "A list of names is a valid configuration of this setting.");
}
}
+38 -1
View File
@@ -361,12 +361,13 @@ Plan for it in these settings:
| Setting | What a partial list costs you |
|---|---|
| `DataApp.HiddenAssistants` | Assistants hidden by the base configuration become **visible** again |
| `DataApp.ProhibitedProviders` | Providers prohibited by the base configuration become **usable** again |
| `DataSourceSecuritySettings.TrustedProviderIds` | Providers trusted by the base configuration lose that status |
| `DataApp.ExternalHttpCustomRootCertificateAllowedHosts` | Hosts of the base configuration stop trusting your root certificates |
| `DataConfidence.CustomConfidenceScheme` | Providers left out fall back to the AI Studio default confidence |
| `DataChat.PreselectedDataSourceIds` | Data sources preselected by the base configuration are no longer preselected |
The rule of thumb: whenever a configuration with a higher priority touches one of these settings, it has to repeat every entry it wants to keep. Watch `DataApp.HiddenAssistants` in particular, because it is the only one in this list that opens something up instead of restricting it.
The rule of thumb: whenever a configuration with a higher priority touches one of these settings, it has to repeat every entry it wants to keep. Watch `DataApp.HiddenAssistants` and `DataApp.ProhibitedProviders` in particular, because a partial list of them opens something up instead of restricting it.
Two settings are the exception and add up instead of replacing:
@@ -424,6 +425,7 @@ Currently, you can configure the following things:
- Any number of LLM providers (self-hosted or cloud providers with encrypted API keys)
- Any number of transcription providers for voice-to-text functionality
- Any number of embedding providers for RAG
- Providers which your organization prohibits, see [Prohibiting providers](#prohibiting-providers)
- Any number of ERI data sources for RAG
- Any number of profiles and chat templates, including the tools and data sources a template brings along
- Any number of policies for the Document Analysis assistant
@@ -1132,6 +1134,41 @@ Two things to keep in mind when you prepare the icon:
`IconPath`, or for the configuration plugin itself through its `icon.lua` -- your organization is
responsible for holding the rights to use it.
## Prohibiting providers
Some organizations must not send any data to certain providers, for example, for legal reasons. List
such providers in your configuration, and AI Studio uses them neither for chats nor for embeddings or
transcriptions:
```lua
CONFIG["SETTINGS"]["DataApp.ProhibitedProviders"] = { "<PROVIDER_KEY>" }
```
Replace `<PROVIDER_KEY>` with the keys of the providers you prohibit, as listed for
`DataConfidence.CustomConfidenceScheme` in the example configuration. A name AI Studio does not know is
ignored and logged as a warning.
- **A prohibition names the operator, not the models.** Prohibiting the API of a model maker keeps AI
Studio away from that API. The models of that maker may remain available through a hub which offers
the models of several makers, and on a server of your own. Prohibit these as well if you need to.
- **Users still find the provider.** When they add a provider and select a prohibited one, the dialog
explains that your organization does not allow it instead of showing the form, and nothing can be
saved.
- **Providers set up before stay.** They remain in the settings, marked as prohibited, and users can
delete them. AI Studio no longer offers them and sends nothing to them: a chat which used one
continues with another provider, and a data source whose embedding provider is prohibited is
neither indexed nor searched until somebody selects another one. Once you lift the prohibition,
these providers work again.
- **A prohibited provider is untrusted.** Its confidence level is always `UNTRUSTED`, whatever the
confidence scheme says. A level which `DataConfidence.CustomConfidenceScheme` names for it is ignored
and logged as a warning, and users cannot change it in the app. Once you lift the prohibition, that
level applies again.
- **The list replaces, it does not add up.** A configuration with a higher priority has to repeat the
providers of the base configuration, see [Settings that hold a list or a table](#settings-that-hold-a-list-or-a-table).
- **Older versions ignore the setting.** It is available since AI Studio v26.10.1. Until all devices
run that version, you can in addition use the `CUSTOM` confidence scheme, set the provider to
`UNTRUSTED` there, and enforce a global minimum confidence level of at least `VERY_LOW`.
## Labeling AI-generated content
Rules on transparency, such as the EU AI Act, may require that a text says when AI generated it, for