Merge branch 'main' into chunk-data

Resolved 29 conflicting files. The notable decisions:

Confidence: main's tool-calling gate (RequiredProviderConfidence) and this
branch's local-RAG gate (DataConfidenceLevel) turned out to be the same rule
on the same axis, so they are now one field. Both tool results and data
sources raise it through RequireProviderConfidence(). The gate checks the
level strictly and no longer exempts providers trusted by configuration:
TrustedProviderIds is documented as applying to data-source security checks
only, and organizations set confidence through DataConfidence
.CustomConfidenceScheme instead. The security axis (DataSecurity, ERI,
IsTrustedForDataSourceSecurityChecks) is unchanged.

Provider creation: main's CreateProvider signature won (hfEndpointKind,
capabilityOverrides, no model parameter); tokenizerPath was added to it and
is set for every provider, including the new Hetzner, IONOS and LiteLLM.
Provider and EmbeddingProvider combine the record parameters, Lua parsing and
Lua serialization of both sides.

File types: main's hierarchy (ODT leaf, WORD parent, PowerPoint without the
legacy .ppt, TABULAR instead of DELIMITED_TABLE) plus this branch's
SPREADSHEET parent with ODS and the xlsm/xlsb/xla/xlam extensions, which the
runtime already reads. Both sides had added a conflicting HTML filter; the
reading family keeps the name, and the export path uses a narrow
HTML_DOCUMENT, following the existing LATEX/TEX split.

Runtime: main's file_data.rs is the base, including the prompt-injection
sanitizer and the extraction routes. Token counting and chunk segmentation
moved into take_released, so they act on the text the filter has released
rather than on text it is still holding. A failed count is logged and left
out instead of ending the extraction, because the app counts such a segment
itself.

Data sources: the participating-provider checks of this branch are kept, and
main's GetAllowedDataSources overload now builds on them. DirectChatService
resolves the launched chat's data source options before the check, so filter
and chat see the same options.

.NET and Rust both build clean; I18N regenerated to 4060 keys.
This commit is contained in:
Thorsten Sommer committed 2026-09-05 21:20:33 +02:00
commit fe35630eff
640 files changed
+45059 -4952

No files matched your search

@@ -5,15 +5,16 @@
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Apps" HeaderText="@T("App Options")">
<ConfigurationSelect OptionDescription="@T("Language behavior")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.LanguageBehavior)" Data="@ConfigurationSelectDataFactory.GetLangBehaviorData()" SelectionUpdate="@(selectedValue => _ = this.UpdateLangBehaviour(selectedValue))" OptionHelp="@T("Select the language behavior for the app. The default is to use the system language. You might want to choose a language manually?")"/>
<ConfigurationSelect OptionDescription="@T("Language behavior")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.LanguageBehavior)" Data="@ConfigurationSelectDataFactory.GetLangBehaviorData()" SelectionUpdate="@(selectedValue => this.UpdateLangBehaviour(selectedValue).Observe(nameof(this.UpdateLangBehaviour)))" OptionHelp="@T("Select the language behavior for the app. The default is to use the system language. You might want to choose a language manually?")"/>
@if (this.SettingsManager.ConfigurationData.App.LanguageBehavior is LangBehavior.MANUAL)
{
<ConfigurationSelect OptionDescription="@T("Language")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.LanguagePluginId)" Data="@ConfigurationSelectDataFactory.GetLanguagesData()" SelectionUpdate="@(selectedValue => _ = this.UpdateManuallySelectedLanguage(selectedValue))" OptionHelp="@T("Select the language for the app.")"/>
<ConfigurationSelect OptionDescription="@T("Language")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.LanguagePluginId)" Data="@ConfigurationSelectDataFactory.GetLanguagesData()" SelectionUpdate="@(selectedValue => this.UpdateManuallySelectedLanguage(selectedValue).Observe(nameof(this.UpdateManuallySelectedLanguage)))" OptionHelp="@T("Select the language for the app.")"/>
}
<ConfigurationSelect OptionDescription="@T("Color theme")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.PreferredTheme)" Data="@ConfigurationSelectDataFactory.GetThemesData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.PreferredTheme = selectedValue)" OptionHelp="@T("Choose the color theme that best suits for you.")"/>
<ConfigurationOption OptionDescription="@T("Save energy?")" LabelOn="@T("Energy saving is enabled")" LabelOff="@T("Energy saving is disabled")" State="@(() => this.SettingsManager.ConfigurationData.App.IsSavingEnergy)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.App.IsSavingEnergy = updatedState)" OptionHelp="@T("When enabled, streamed content from the AI is updated once every third second. When disabled, streamed content will be updated as soon as it is available.")"/>
<ConfigurationOption OptionDescription="@T("Enable spellchecking?")" LabelOn="@T("Spellchecking is enabled")" LabelOff="@T("Spellchecking is disabled")" State="@(() => this.SettingsManager.ConfigurationData.App.EnableSpellchecking)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.App.EnableSpellchecking = updatedState)" OptionHelp="@T("When enabled, spellchecking will be active in all input fields. Depending on your operating system, errors may not be visually highlighted, but right-clicking may still offer possible corrections.")"/>
<ConfigurationOption OptionDescription="@T("Show details when suspicious content was removed?")" LabelOn="@T("A dialog lists what was removed and explains the attack pattern")" LabelOff="@T("Only a short notification is shown")" State="@(() => this.SettingsManager.ConfigurationData.App.ShowPromptInjectionAlert)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.App.ShowPromptInjectionAlert = updatedState)" OptionHelp="@T("Shows a dialog listing the removed passages, together with an explanation and an external reference.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.ShowPromptInjectionAlert, out var meta) && meta.IsLocked"/>
<ConfigurationSlider T="int" OptionDescription="@T("Request timeout")" Min="@ExternalHttpClientTimeout.MIN_HTTP_CLIENT_TIMEOUT_SECONDS" Max="@ExternalHttpClientTimeout.MAX_HTTP_CLIENT_TIMEOUT_SECONDS" Step="60" Unit="@T("seconds")" Value="@(() => this.SettingsManager.ConfigurationData.App.HttpClientTimeoutSeconds)" ValueUpdate="@(updatedValue => this.SettingsManager.ConfigurationData.App.HttpClientTimeoutSeconds = updatedValue)" OptionHelp="@T("How long AI Studio waits for external HTTP requests, such as AI providers, embeddings, transcription, ERI data sources, and enterprise configuration downloads.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.HttpClientTimeoutSeconds, out var meta) && meta.IsLocked"/>
<ConfigurationSelect OptionDescription="@T("Check for updates")" SelectedValue="@(() => this.DisplayedUpdateInterval)" Data="@ConfigurationSelectDataFactory.GetManagedUpdateIntervalData(this.DisplayedUpdateInterval)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.UpdateInterval = selectedValue)" OptionHelp="@this.UpdateIntervalHelp" IsLocked="@this.IsUpdateIntervalLocked"/>
<ConfigurationSelect OptionDescription="@T("Update installation method")" SelectedValue="@(() => this.DisplayedUpdateInstallation)" Data="@ConfigurationSelectDataFactory.GetUpdateBehaviourData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.UpdateInstallation = selectedValue)" OptionHelp="@this.UpdateInstallationHelp" IsLocked="@this.IsUpdateInstallationLocked"/>
@@ -27,7 +28,7 @@
var availablePreviewFeatures = ConfigurationSelectDataFactory.GetPreviewFeaturesData(this.SettingsManager).ToList();
if (availablePreviewFeatures.Count > 0)
{
<ConfigurationMultiSelect OptionDescription="@T("Select preview features")" SelectedValues="@this.GetSelectedPreviewFeatures" Data="@availablePreviewFeatures" SelectionUpdate="@this.UpdateEnabledPreviewFeatures" OptionHelp="@T("Which preview features would you like to enable?")" IsItemLocked="@this.IsPluginContributedPreviewFeature" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.EnabledPreviewFeatures, out var meta) && meta.IsLocked"/>
<ConfigurationMultiSelect OptionDescription="@T("Select preview features")" SelectedValues="@this.GetSelectedPreviewFeatures" Data="@availablePreviewFeatures" SelectionUpdate="@this.UpdateEnabledPreviewFeatures" OptionHelp="@T("Which preview features would you like to enable?")" IsItemLocked="@this.IsPluginContributedPreviewFeature" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.EnabledPreviewFeatures, out var meta) && meta.IsLocked" EmptySelectionText="@T("No preview features selected.")" SingleSelectionText="@T("You have selected 1 preview feature.")" MultipleSelectionText="@T("You have selected {0} preview features.")"/>
}
}
@@ -36,7 +37,18 @@
@if (PreviewFeatures.PRE_SPEECH_TO_TEXT_2026.IsEnabled(this.SettingsManager))
{
<ConfigurationSelect OptionDescription="@T("Select a transcription provider")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.UseTranscriptionProvider)" Data="@this.GetFilteredTranscriptionProviders()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.UseTranscriptionProvider = selectedValue)" OptionHelp="@T("Select a transcription provider for transcribing your voice. Without a selected provider, dictation and transcription features will be disabled.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.UseTranscriptionProvider, out var meta) && meta.IsLocked"/>
<ConfigurationSelect OptionDescription="@T("Select a transcription provider")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.UseTranscriptionProvider)" Data="@this.GetFilteredTranscriptionProviders()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.UseTranscriptionProvider = selectedValue)" OptionHelp="@T("Select a transcription provider for transcribing your voice. Without a selected provider, dictation and transcription features will be disabled.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.UseTranscriptionProvider, out var meta) && meta.IsLocked">
<ItemTemplate Context="providerData">
@if (this.GetTranscriptionProvider(providerData.Value) is { } provider)
{
<ProviderLabel ProviderType="@provider.UsedLLMProvider" CustomIconDataUrl="@provider.CustomIconDataUrl" Text="@providerData.Name" />
}
else
{
@providerData.Name
}
</ItemTemplate>
</ConfigurationSelect>
<ConfigurationShortcut Data="@this.VoiceRecordingShortcut" OptionDescription="@T("Voice recording shortcut")" OptionHelp="@T("The global keyboard shortcut for toggling voice recording. This shortcut works system-wide, even when the app is not focused.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.ShortcutVoiceRecording, out var meta) && meta.IsLocked"/>
}
@@ -15,11 +15,13 @@ public partial class SettingsPanelApp : SettingsPanelBase
private UpdatePolicyMode updatePolicyMode;
private UpdateInterval DisplayedUpdateInterval => this.updatePolicyMode is UpdatePolicyMode.FLATPAK
private bool CannotUpdateItself => this.updatePolicyMode is UpdatePolicyMode.FLATPAK or UpdatePolicyMode.MANAGED_INSTALLATION or UpdatePolicyMode.UNSUPPORTED_INSTALLATION_LOCATION or UpdatePolicyMode.DEVELOPMENT;
private UpdateInterval DisplayedUpdateInterval => this.CannotUpdateItself
? UpdateInterval.NO_CHECK
: this.SettingsManager.ConfigurationData.App.UpdateInterval;
private UpdateInstallation DisplayedUpdateInstallation => this.updatePolicyMode is UpdatePolicyMode.FLATPAK
private UpdateInstallation DisplayedUpdateInstallation => this.CannotUpdateItself
? UpdateInstallation.MANUAL
: this.SettingsManager.ConfigurationData.App.UpdateInstallation;
@@ -27,20 +29,26 @@ public partial class SettingsPanelApp : SettingsPanelBase
{
UpdatePolicyMode.ENTERPRISE_DISABLED => T("Your organization has disabled update checks and installations."),
UpdatePolicyMode.FLATPAK => T("AI Studio cannot check for updates when running as a Flatpak. Updates are managed outside the app."),
UpdatePolicyMode.MANAGED_INSTALLATION => T("This installation does not check for updates itself. Contact the person or organization that installed AI Studio for update information."),
UpdatePolicyMode.UNSUPPORTED_INSTALLATION_LOCATION => T("AI Studio cannot update itself from its current location, so it does not check for updates."),
UpdatePolicyMode.DEVELOPMENT => T("Development builds do not check for updates."),
_ => T("How often should we check for app updates?")
};
private string UpdateInstallationHelp => this.updatePolicyMode switch
{
UpdatePolicyMode.ENTERPRISE_DISABLED => T("This setting has no effect while updates are disabled by your organization."),
UpdatePolicyMode.FLATPAK => T("AI Studio cannot install updates when running as a Flatpak. Use the update method provided by your Flatpak distribution."),
UpdatePolicyMode.FLATPAK => T("AI Studio cannot install updates when running as a Flatpak. Update it using the Flatpak source or bundle from which you installed it."),
UpdatePolicyMode.MANAGED_INSTALLATION => T("AI Studio cannot install updates into this installation. Contact the person or organization that installed it for new versions."),
UpdatePolicyMode.UNSUPPORTED_INSTALLATION_LOCATION => T("AI Studio cannot install updates into its current installation location. Install new versions yourself."),
UpdatePolicyMode.DEVELOPMENT => T("Development builds do not install updates."),
_ => T("Should updates be installed automatically or manually?")
};
private bool IsUpdateIntervalLocked() => this.updatePolicyMode is UpdatePolicyMode.ENTERPRISE_DISABLED or UpdatePolicyMode.FLATPAK ||
private bool IsUpdateIntervalLocked() => this.updatePolicyMode is UpdatePolicyMode.ENTERPRISE_DISABLED || this.CannotUpdateItself ||
ManagedConfiguration.TryGet(x => x.App, x => x.UpdateInterval, out var meta) && meta.IsLocked;
private bool IsUpdateInstallationLocked() => this.updatePolicyMode is UpdatePolicyMode.ENTERPRISE_DISABLED or UpdatePolicyMode.FLATPAK ||
private bool IsUpdateInstallationLocked() => this.updatePolicyMode is UpdatePolicyMode.ENTERPRISE_DISABLED || this.CannotUpdateItself ||
ManagedConfiguration.TryGet(x => x.App, x => x.UpdateInstallation, out var meta) && meta.IsLocked;
protected override async Task OnInitializedAsync()
@@ -91,13 +99,19 @@ public partial class SettingsPanelApp : SettingsPanelBase
yield return new(T("Disable dictation and transcription"), string.Empty);
var minimumLevel = this.SettingsManager.GetMinimumConfidenceLevel(Tools.Components.APP_SETTINGS);
foreach (var provider in this.SettingsManager.ConfigurationData.TranscriptionProviders)
foreach (var provider in this.SettingsManager.GetAllTranscriptionProviders())
{
if (provider.UsedLLMProvider.GetConfidence(this.SettingsManager).Level >= minimumLevel)
yield return new(provider.Name, provider.Id);
}
}
private TranscriptionProvider? GetTranscriptionProvider(string providerId)
{
var provider = this.SettingsManager.GetTranscriptionProviderById(providerId);
return provider == TranscriptionProvider.NONE ? null : provider;
}
private void UpdatePreviewFeatures(PreviewVisibility previewVisibility)
{
this.SettingsManager.ConfigurationData.App.PreviewVisibility = previewVisibility;
@@ -108,8 +122,10 @@ public partial class SettingsPanelApp : SettingsPanelBase
private HashSet<PreviewFeatures> GetPluginContributedPreviewFeatures()
{
// Several configuration plugins may contribute at the same time, e.g. one preview feature
// for the whole organization and another one for a single department:
if (ManagedConfiguration.TryGet(x => x.App, x => x.EnabledPreviewFeatures, out var meta) && meta.HasPluginContribution)
return meta.PluginContribution.Where(x => !x.IsReleased()).ToHashSet();
return meta.PluginContributions.Values.SelectMany(contribution => contribution).Where(x => !x.IsReleased()).ToHashSet();
return [];
}
@@ -122,7 +138,7 @@ public partial class SettingsPanelApp : SettingsPanelBase
if (!ManagedConfiguration.TryGet(x => x.App, x => x.EnabledPreviewFeatures, out var meta) || !meta.HasPluginContribution)
return false;
return meta.PluginContribution.Contains(feature);
return meta.PluginContributions.Values.Any(contribution => contribution.Contains(feature));
}
private HashSet<PreviewFeatures> GetSelectedPreviewFeatures()
@@ -17,41 +17,54 @@
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
@T("This helps AI Studio understand and compare things in a way that's similar to how humans do. When you're working on something, AI Studio can automatically identify related documents and data by comparing their digital fingerprints. For instance, if you're writing about customer service, AI Studio can instantly find other documents in your data that discuss similar topics or experiences, even if they use different words.")
</MudJustifiedText>
<MudTable Items="@this.SettingsManager.ConfigurationData.EmbeddingProviders" Hover="@true" Class="border-dashed border rounded-lg">
<MudTable Items="@this.SettingsManager.GetAllEmbeddingProviders()" Hover="@true" GroupBy="@GROUP_CONFIG" Class="border-dashed border rounded-lg">
<ColGroup>
<col style="width: 3em;"/>
<col style="width: 12em;"/>
<col style="width: 12em;"/>
<col/>
<col style="width: 22em;"/>
</ColGroup>
<HeaderContent>
<MudTh>#</MudTh>
<MudTh>@T("Name")</MudTh>
<MudTh>@T("Provider")</MudTh>
<MudTh>@T("Model")</MudTh>
<MudTh>@T("Actions")</MudTh>
</HeaderContent>
<GroupHeaderTemplate>
<MudTh Class="provider-group-header" colspan="4">
@if (context.Key is LLMProviders llmProvider)
{
<ProviderLabel ProviderType="@llmProvider" Text="@llmProvider.ToName()" />
}
</MudTh>
</GroupHeaderTemplate>
<RowTemplate>
<MudTd>@context.Num</MudTd>
<MudTd>@context.Name</MudTd>
<MudTd>@context.UsedLLMProvider.ToName()</MudTd>
<MudTd>
<ProviderLabel ProviderType="@context.UsedLLMProvider" CustomIconDataUrl="@context.CustomIconDataUrl" Text="@context.Name" />
</MudTd>
<MudTd>@this.GetEmbeddingProviderModelName(context)</MudTd>
<MudTd>
<MudStack Row="true" Class="mb-2 mt-2" Spacing="1" Wrap="Wrap.Wrap">
@if (context.IsTrustedByConfiguration(this.SettingsManager))
@if (context.IsTrustedForDataSourceSecurityChecks(this.SettingsManager))
{
<MudTooltip Text="@T("This embedding provider is trusted by your organization for data source security checks. Local data can be sent to it without security warnings.")">
<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."))">
<MudIconButton Color="Color.Success" Icon="@Icons.Material.Filled.VerifiedUser" Disabled="true"/>
</MudTooltip>
}
@if (context.IsEnterpriseConfiguration)
@if (context.IsEnterpriseConfiguration && !context.AllowUserProvidedAPIKey)
{
<MudTooltip Text="@T("This embedding provider is managed by your organization.")">
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Business" Disabled="true"/>
</MudTooltip>
}
else if (context.IsEnterpriseConfiguration && context.AllowUserProvidedAPIKey)
{
<MudTooltip Text="@T("This embedding provider is managed by your organization. You can set your own API key.")">
<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))"/>
</MudTooltip>
}
else
{
<MudTooltip Text="@T("Open Dashboard")">
@@ -60,12 +73,7 @@
<MudTooltip Text="@T("Edit")">
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Edit" OnClick="@(() => this.EditEmbeddingProvider(context))"/>
</MudTooltip>
@if (this.SettingsManager.ConfigurationData.App.ShowAdminSettings)
{
<MudTooltip Text="@T("Export configuration")">
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Dataset" OnClick="@(() => this.ExportEmbeddingProvider(context))"/>
</MudTooltip>
}
<AdminExportButton OnClick="@(() => this.ExportEmbeddingProvider(context))" />
<MudTooltip Text="@T("Delete")">
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Delete" OnClick="@(() => this.DeleteEmbeddingProvider(context))"/>
</MudTooltip>
@@ -16,6 +16,17 @@ public partial class SettingsPanelEmbeddings : SettingsPanelProviderBase
[Inject]
private DataSourceEmbeddingService DataSourceEmbeddingService { get; init; } = null!;
/// <summary>
/// Groups the table by the used LLM provider. The embedding provider list is already sorted by
/// that provider, so all instances of one LLM provider form a single, coherent group.
/// </summary>
private static readonly TableGroupDefinition<EmbeddingProvider> GROUP_CONFIG = new()
{
Expandable = true,
IsInitiallyExpanded = false,
Selector = provider => provider.UsedLLMProvider,
};
[Parameter]
public List<ConfigurationSelectData<string>> AvailableEmbeddingProviders { get; set; } = new();
@@ -68,12 +79,16 @@ public partial class SettingsPanelEmbeddings : SettingsPanelProviderBase
private async Task EditEmbeddingProvider(EmbeddingProvider embeddingProvider)
{
if (embeddingProvider.IsEnterpriseConfiguration && !embeddingProvider.AllowUserProvidedAPIKey)
return;
var dialogParameters = new DialogParameters<EmbeddingProviderDialog>
{
{ x => x.DataNum, embeddingProvider.Num },
{ x => x.DataId, embeddingProvider.Id },
{ x => x.DataName, embeddingProvider.Name },
{ x => x.DataLLMProvider, embeddingProvider.UsedLLMProvider },
{ x => x.DataCustomIconDataUrl, embeddingProvider.CustomIconDataUrl },
{ x => x.DataModel, embeddingProvider.Model },
{ x => x.DataHostname, embeddingProvider.Hostname },
{ x => x.IsSelfHosted, embeddingProvider.IsSelfHosted },
@@ -82,6 +97,8 @@ public partial class SettingsPanelEmbeddings : SettingsPanelProviderBase
{ x => x.DataTokenizerPath, embeddingProvider.TokenizerPath },
{ x => x.DataTokenLimit, embeddingProvider.EffectiveTokenLimit },
{ x => x.DataEmbeddingBatchSize, embeddingProvider.EffectiveEmbeddingBatchSize },
{ x => x.HFInferenceProviderId, embeddingProvider.HFInferenceProvider },
{ x => x.IsEnterpriseConfiguration, embeddingProvider.IsEnterpriseConfiguration },
};
var dialogReference = await this.DialogService.ShowAsync<EmbeddingProviderDialog>(T("Edit Embedding Provider"), dialogParameters, DialogOptions.FULLSCREEN);
@@ -89,6 +106,16 @@ public partial class SettingsPanelEmbeddings : SettingsPanelProviderBase
if (dialogResult is null || dialogResult.Canceled)
return;
if (embeddingProvider.IsEnterpriseConfiguration)
{
// Only the API key changed, and the dialog already stored it directly. The provider
// object itself is managed by the configuration plugin and must not be overwritten
// with the dialog's copy -- doing so would let the locked-but-technically-editable
// fields drift from what the organization configured.
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
return;
}
var editedEmbeddingProvider = (EmbeddingProvider)dialogResult.Data!;
// Set the provider number if it's not set. This is important for providers
@@ -170,7 +197,7 @@ public partial class SettingsPanelEmbeddings : SettingsPanelProviderBase
private async Task UpdateEmbeddingProviders()
{
this.AvailableEmbeddingProviders.Clear();
foreach (var provider in this.SettingsManager.ConfigurationData.EmbeddingProviders)
foreach (var provider in this.SettingsManager.GetAllEmbeddingProviders())
this.AvailableEmbeddingProviders.Add(new (provider.Name, provider.Id));
await this.AvailableEmbeddingProvidersChanged.InvokeAsync(this.AvailableEmbeddingProviders);
@@ -9,40 +9,50 @@
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
@T("What we call a provider is the combination of an LLM provider such as OpenAI and a model like GPT-4o. You can configure as many providers as you want. This way, you can use the appropriate model for each task. As an LLM provider, you can also choose local providers. However, to use this app, you must configure at least one provider.")
</MudJustifiedText>
<MudTable Items="@this.SettingsManager.ConfigurationData.Providers" Hover="@true" Class="border-dashed border rounded-lg">
<MudTable Items="@this.SettingsManager.GetAllProviders()" Hover="@true" GroupBy="@GROUP_CONFIG" Class="border-dashed border rounded-lg">
<ColGroup>
<col style="width: 3em;"/>
<col style="width: 12em;"/>
<col style="width: 12em;"/>
<col/>
<col style="width: 22em;"/>
</ColGroup>
<HeaderContent>
<MudTh>#</MudTh>
<MudTh>@T("Instance Name")</MudTh>
<MudTh>@T("Provider")</MudTh>
<MudTh>@T("Model")</MudTh>
<MudTh>@T("Actions")</MudTh>
</HeaderContent>
<GroupHeaderTemplate>
<MudTh Class="provider-group-header" colspan="4">
@if (context.Key is LLMProviders llmProvider)
{
<ProviderLabel ProviderType="@llmProvider" Text="@llmProvider.ToName()" />
}
</MudTh>
</GroupHeaderTemplate>
<RowTemplate>
<MudTd>@context.Num</MudTd>
<MudTd>@context.InstanceName</MudTd>
<MudTd>@context.UsedLLMProvider.ToName()</MudTd>
<MudTd>
<ProviderLabel ProviderSettings="@context" Text="@context.InstanceName" />
</MudTd>
<MudTd>@this.GetLLMProviderModelName(context)</MudTd>
<MudTd>
<MudStack Row="true" Class="mb-2 mt-2" Spacing="1" Wrap="Wrap.Wrap">
@if (context.IsTrustedByConfiguration(this.SettingsManager))
@if (context.IsTrustedForDataSourceSecurityChecks(this.SettingsManager))
{
<MudTooltip Text="@T("This provider is trusted by your organization for data source security checks.")">
<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."))">
<MudIconButton Color="Color.Success" Icon="@Icons.Material.Filled.VerifiedUser" Disabled="true"/>
</MudTooltip>
}
@if (context.IsEnterpriseConfiguration)
@if (context.IsEnterpriseConfiguration && !context.AllowUserProvidedAPIKey)
{
<MudTooltip Text="@T("This provider is managed by your organization.")">
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Business" Disabled="true"/>
</MudTooltip>
}
else if (context.IsEnterpriseConfiguration && context.AllowUserProvidedAPIKey)
{
<MudTooltip Text="@T("This provider is managed by your organization. You can set your own API key.")">
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Key" OnClick="@(() => this.EditLLMProvider(context))"/>
</MudTooltip>
}
else
{
<MudTooltip Text="@T("Open Dashboard")">
@@ -51,12 +61,7 @@
<MudTooltip Text="@T("Edit")">
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Edit" OnClick="@(() => this.EditLLMProvider(context))"/>
</MudTooltip>
@if (this.SettingsManager.ConfigurationData.App.ShowAdminSettings)
{
<MudTooltip Text="@T("Export configuration")">
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Dataset" OnClick="@(() => this.ExportLLMProvider(context))"/>
</MudTooltip>
}
<AdminExportButton OnClick="@(() => this.ExportLLMProvider(context))" />
<MudTooltip Text="@T("Delete")">
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Delete" OnClick="@(() => this.DeleteLLMProvider(context))"/>
</MudTooltip>
@@ -66,7 +71,7 @@
</RowTemplate>
</MudTable>
@if(this.SettingsManager.ConfigurationData.Providers.Count == 0)
@if(this.SettingsManager.GetAllProviders().Count == 0)
{
<MudText Typo="Typo.h6" Class="mt-3">
@T("No providers configured yet.")
@@ -13,6 +13,17 @@ namespace AIStudio.Components.Settings;
public partial class SettingsPanelProviders : SettingsPanelProviderBase
{
/// <summary>
/// Groups the table by the used LLM provider. The provider list is already sorted by that
/// provider, so all instances of one LLM provider form a single, coherent group.
/// </summary>
private static readonly TableGroupDefinition<AIStudio.Settings.Provider> GROUP_CONFIG = new()
{
Expandable = true,
IsInitiallyExpanded = false,
Selector = provider => provider.UsedLLMProvider,
};
[Parameter]
public List<ConfigurationSelectData<string>> AvailableLLMProviders { get; set; } = new();
@@ -29,7 +40,7 @@ public partial class SettingsPanelProviders : SettingsPanelProviderBase
#endregion
[SuppressMessage("Usage", "MWAIS0001:Direct access to `Providers` is not allowed")]
[SuppressMessage("Usage", "MWAIS0001:Direct access to `Providers` is not allowed", Justification = "Managing the provider list is the purpose of this settings panel. Reading providers goes through the settings manager, but adding, editing, and removing them stays here on purpose.")]
private async Task AddLLMProvider()
{
var dialogParameters = new DialogParameters<ProviderDialog>
@@ -52,21 +63,22 @@ public partial class SettingsPanelProviders : SettingsPanelProviderBase
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
}
[SuppressMessage("Usage", "MWAIS0001:Direct access to `Providers` is not allowed")]
[SuppressMessage("Usage", "MWAIS0001:Direct access to `Providers` is not allowed", Justification = "Managing the provider list is the purpose of this settings panel. Reading providers goes through the settings manager, but adding, editing, and removing them stays here on purpose.")]
private async Task EditLLMProvider(AIStudio.Settings.Provider provider)
{
if(provider == AIStudio.Settings.Provider.NONE)
return;
if (provider.IsEnterpriseConfiguration)
if (provider.IsEnterpriseConfiguration && !provider.AllowUserProvidedAPIKey)
return;
var dialogParameters = new DialogParameters<ProviderDialog>
{
{ x => x.DataNum, provider.Num },
{ x => x.DataId, provider.Id },
{ x => x.DataInstanceName, provider.InstanceName },
{ x => x.DataLLMProvider, provider.UsedLLMProvider },
{ x => x.DataCustomIconDataUrl, provider.CustomIconDataUrl },
{ x => x.DataModel, provider.Model },
{ x => x.DataHostname, provider.Hostname },
{ x => x.IsSelfHosted, provider.IsSelfHosted },
@@ -76,6 +88,7 @@ public partial class SettingsPanelProviders : SettingsPanelProviderBase
{ x => x.AdditionalJsonApiParameters, provider.AdditionalJsonApiParameters },
{ x => x.DataTokenizerPath, provider.TokenizerPath },
{ x => x.DataCapabilityOverrides, provider.CapabilityOverrides },
{ x => x.IsEnterpriseConfiguration, provider.IsEnterpriseConfiguration },
};
var dialogReference = await this.DialogService.ShowAsync<ProviderDialog>(T("Edit LLM Provider"), dialogParameters, DialogOptions.FULLSCREEN);
@@ -83,21 +96,31 @@ public partial class SettingsPanelProviders : SettingsPanelProviderBase
if (dialogResult is null || dialogResult.Canceled)
return;
if (provider.IsEnterpriseConfiguration)
{
// Only the API key changed, and the dialog already stored it directly. The provider
// object itself is managed by the configuration plugin and must not be overwritten
// with the dialog's copy -- doing so would let the locked-but-technically-editable
// fields drift from what the organization configured.
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
return;
}
var editedProvider = (AIStudio.Settings.Provider)dialogResult.Data!;
// Set the provider number if it's not set. This is important for providers
// added before we started saving the provider number.
if(editedProvider.Num == 0)
editedProvider = editedProvider with { Num = this.SettingsManager.ConfigurationData.NextProviderNum++ };
this.SettingsManager.ConfigurationData.Providers[this.SettingsManager.ConfigurationData.Providers.IndexOf(provider)] = editedProvider;
await this.UpdateProviders();
await this.SettingsManager.StoreSettings();
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
}
[SuppressMessage("Usage", "MWAIS0001:Direct access to `Providers` is not allowed")]
[SuppressMessage("Usage", "MWAIS0001:Direct access to `Providers` is not allowed", Justification = "Managing the provider list is the purpose of this settings panel. Reading providers goes through the settings manager, but adding, editing, and removing them stays here on purpose.")]
private async Task DeleteLLMProvider(AIStudio.Settings.Provider provider)
{
var dialogParameters = new DialogParameters<ConfirmDialog>
@@ -172,11 +195,10 @@ public partial class SettingsPanelProviders : SettingsPanelProviderBase
return modelName.Length > MAX_LENGTH ? "[...] " + modelName[^Math.Min(MAX_LENGTH, modelName.Length)..] : modelName;
}
[SuppressMessage("Usage", "MWAIS0001:Direct access to `Providers` is not allowed")]
private async Task UpdateProviders()
{
this.AvailableLLMProviders.Clear();
foreach (var provider in this.SettingsManager.ConfigurationData.Providers)
foreach (var provider in this.SettingsManager.GetAllProviders())
this.AvailableLLMProviders.Add(new (provider.InstanceName, provider.Id));
await this.AvailableLLMProvidersChanged.InvokeAsync(this.AvailableLLMProviders);
@@ -0,0 +1,65 @@
@inherits SettingsPanelBase
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Build" HeaderText="@T("Tool Settings")">
<MudText Typo="Typo.body1" Class="mb-4">
@T("Configure global settings for each tool.")
</MudText>
<MudTable Items="@this.items" Hover="@true" Dense="@true">
<HeaderContent>
<MudTh>@T("Icon")</MudTh>
<MudTh>@T("Name")</MudTh>
<MudTh>@T("Description")</MudTh>
<MudTh>@T("Minimum provider confidence")</MudTh>
<MudTh>@T("Status")</MudTh>
<MudTh>@T("Settings")</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd>
<MudIcon Icon="@context.Implementation.Icon" Color="Color.Info" />
</MudTd>
<MudTd>
<MudText Typo="Typo.body1">@context.Implementation.GetDisplayName()</MudText>
</MudTd>
<MudTd>
<MudText Typo="Typo.body2">@context.Implementation.GetDescription()</MudText>
</MudTd>
<MudTd>
<MudMenu StartIcon="@Icons.Material.Filled.Security" EndIcon="@Icons.Material.Filled.KeyboardArrowDown" Label="@this.GetCurrentConfidenceLevelName(context)" Variant="Variant.Filled" Style="@this.SetCurrentConfidenceLevelColorStyle(context)" Disabled="@this.IsToolConfidenceManaged()">
@foreach (var confidenceLevel in this.GetSelectableConfidenceLevels())
{
<MudMenuItem OnClick="@(async () => await this.ChangeMinimumProviderConfidence(context, confidenceLevel))">
@this.GetConfidenceLevelName(confidenceLevel)
</MudMenuItem>
}
</MudMenu>
</MudTd>
<MudTd>
@if (!context.IsActive)
{
<MudTooltip Text="@T("This tool has been disabled by your organization.")">
<MudIcon Icon="@Icons.Material.Filled.Lock" Color="Color.Error" />
</MudTooltip>
}
else if (context.ConfigurationState.IsConfigured)
{
<MudIcon Icon="@Icons.Material.Filled.CheckCircle" Color="Color.Success" />
}
else
{
<MudTooltip Text="@this.GetConfigurationTooltip(context)">
<MudIcon Icon="@Icons.Material.Filled.Warning" Color="Color.Warning" />
</MudTooltip>
}
</MudTd>
<MudTd>
<MudStack Row="true" Wrap="Wrap.NoWrap" AlignItems="AlignItems.Center" Spacing="0">
<MudTooltip Text="@T("Settings")">
<MudIconButton Icon="@Icons.Material.Filled.Settings" OnClick="@(async () => await this.OpenSettings(context.Definition.Id))" aria-label="@T("Settings")" />
</MudTooltip>
<AdminExportButton OnClick="@(() => this.OpenExport(context.Definition.Id))" />
</MudStack>
</MudTd>
</RowTemplate>
</MudTable>
</ExpansionPanel>
@@ -0,0 +1,102 @@
using AIStudio.Provider;
using AIStudio.Dialogs.Settings;
using AIStudio.Settings;
using AIStudio.Tools.ToolCallingSystem;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components.Settings;
public partial class SettingsPanelTools : SettingsPanelBase
{
[Inject]
private ToolRegistry ToolRegistry { get; init; } = null!;
private IReadOnlyList<ToolCatalogItem> items = [];
protected override async Task OnInitializedAsync()
{
this.ApplyFilters([], [ Event.CONFIGURATION_CHANGED ]);
this.items = await this.ToolRegistry.GetCatalogAsync(this.ToolRegistry.GetAllDefinitions());
await base.OnInitializedAsync();
}
private async Task OpenSettings(string toolId)
{
var parameters = new DialogParameters<ToolSettingsDialog>
{
{ x => x.ToolId, toolId },
};
var dialog = await this.DialogService.ShowAsync<ToolSettingsDialog>(null, parameters, Dialogs.DialogOptions.FULLSCREEN);
await dialog.Result;
this.items = await this.ToolRegistry.GetCatalogAsync(this.ToolRegistry.GetAllDefinitions());
this.StateHasChanged();
}
private async Task OpenExport(string toolId)
{
if (!this.SettingsManager.ConfigurationData.App.ShowAdminSettings)
return;
var parameters = new DialogParameters<ToolSettingsExportDialog>
{
{ x => x.ToolId, toolId },
};
await this.DialogService.ShowAsync<ToolSettingsExportDialog>(null, parameters, Dialogs.DialogOptions.FULLSCREEN);
}
private string GetConfigurationTooltip(ToolCatalogItem item) => item.ConfigurationState.MissingRequiredFields.Count switch
{
_ when !string.IsNullOrWhiteSpace(item.ConfigurationState.Message) => item.ConfigurationState.Message,
0 => this.T("This tool still needs to be configured."),
_ => string.Format(this.T("Missing required settings: {0}"), string.Join(", ", item.ConfigurationState.MissingRequiredFields.Select(fieldName => this.GetFieldDisplayName(item, fieldName))))
};
private string GetFieldDisplayName(ToolCatalogItem item, string fieldName)
{
var fieldDefinition = item.Definition.SettingsSchema.Properties.GetValueOrDefault(fieldName);
if (fieldDefinition is null)
return fieldName;
return item.Implementation.GetSettingsFieldLabel(fieldName, fieldDefinition);
}
private IEnumerable<ConfidenceLevel> GetSelectableConfidenceLevels() =>
Enum.GetValues<ConfidenceLevel>().OrderBy(x => x).Where(x => x is not ConfidenceLevel.UNKNOWN);
private string GetCurrentConfidenceLevelName(ToolCatalogItem item) => this.GetConfidenceLevelName(GetMinimumProviderConfidence(item));
private string GetConfidenceLevelName(ConfidenceLevel confidenceLevel) => confidenceLevel is ConfidenceLevel.NONE
? this.T("No minimum confidence level chosen")
: confidenceLevel.GetName();
private string SetCurrentConfidenceLevelColorStyle(ToolCatalogItem item) =>
$"background-color: {GetMinimumProviderConfidence(item).GetColor(this.SettingsManager)};";
private bool IsToolConfidenceManaged() =>
ManagedConfiguration.TryGet(x => x.Tools, x => x.MinimumProviderConfidenceByToolId, out var meta) && meta.IsLocked;
// The catalog already carries the resolved level, so there is nothing to look up again:
private static ConfidenceLevel GetMinimumProviderConfidence(ToolCatalogItem item) => item.MinimumProviderConfidence;
private async Task ChangeMinimumProviderConfidence(ToolCatalogItem item, ConfidenceLevel confidenceLevel)
{
this.SettingsManager.SetMinimumProviderConfidenceForTool(item.Definition.Id, confidenceLevel, item.Definition.MinimumProviderConfidence);
await this.SettingsManager.StoreSettings();
this.items = await this.ToolRegistry.GetCatalogAsync(this.ToolRegistry.GetAllDefinitions());
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
}
protected override async Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
{
switch (triggeredEvent)
{
case Event.CONFIGURATION_CHANGED:
this.items = await this.ToolRegistry.GetCatalogAsync(this.ToolRegistry.GetAllDefinitions());
await this.InvokeAsync(this.StateHasChanged);
break;
}
}
}
@@ -13,41 +13,51 @@
@T("With the support of transcription models, MindWork AI Studio can convert human speech into text. This is useful, for example, when you need to dictate text. You can choose from dedicated transcription models, but not multimodal LLMs (large language models) that can handle both speech and text. The configuration of multimodal models is done in the 'Configure providers' section.")
</MudJustifiedText>
<MudTable Items="@this.SettingsManager.ConfigurationData.TranscriptionProviders" Hover="@true" Class="border-dashed border rounded-lg">
<MudTable Items="@this.SettingsManager.GetAllTranscriptionProviders()" Hover="@true" GroupBy="@GROUP_CONFIG" Class="border-dashed border rounded-lg">
<ColGroup>
<col style="width: 3em;"/>
<col style="width: 12em;"/>
<col style="width: 12em;"/>
<col/>
<col style="width: 22em;"/>
</ColGroup>
<HeaderContent>
<MudTh>#</MudTh>
<MudTh>@T("Name")</MudTh>
<MudTh>@T("Provider")</MudTh>
<MudTh>@T("Model")</MudTh>
<MudTh>@T("Actions")</MudTh>
</HeaderContent>
<GroupHeaderTemplate>
<MudTh Class="provider-group-header" colspan="4">
@if (context.Key is LLMProviders llmProvider)
{
<ProviderLabel ProviderType="@llmProvider" Text="@llmProvider.ToName()" />
}
</MudTh>
</GroupHeaderTemplate>
<RowTemplate>
<MudTd>@context.Num</MudTd>
<MudTd>@context.Name</MudTd>
<MudTd>@context.UsedLLMProvider.ToName()</MudTd>
<MudTd>
<ProviderLabel ProviderType="@context.UsedLLMProvider" CustomIconDataUrl="@context.CustomIconDataUrl" Text="@context.Name" />
</MudTd>
<MudTd>@this.GetTranscriptionProviderModelName(context)</MudTd>
<MudTd>
<MudStack Row="true" Class="mb-2 mt-2" Spacing="1" Wrap="Wrap.Wrap">
@if (context.IsTrustedByConfiguration(this.SettingsManager))
@if (context.IsTrustedForDataSourceSecurityChecks(this.SettingsManager))
{
<MudTooltip Text="@T("This transcription provider is trusted by your organization for data source security checks.")">
<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."))">
<MudIconButton Color="Color.Success" Icon="@Icons.Material.Filled.VerifiedUser" Disabled="true"/>
</MudTooltip>
}
@if (context.IsEnterpriseConfiguration)
@if (context.IsEnterpriseConfiguration && !context.AllowUserProvidedAPIKey)
{
<MudTooltip Text="@T("This transcription provider is managed by your organization.")">
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Business" Disabled="true"/>
</MudTooltip>
}
else if (context.IsEnterpriseConfiguration && context.AllowUserProvidedAPIKey)
{
<MudTooltip Text="@T("This transcription provider is managed by your organization. You can set your own API key.")">
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Key" OnClick="@(() => this.EditTranscriptionProvider(context))"/>
</MudTooltip>
}
else
{
<MudTooltip Text="@T("Open Dashboard")">
@@ -56,12 +66,7 @@
<MudTooltip Text="@T("Edit")">
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Edit" OnClick="@(() => this.EditTranscriptionProvider(context))"/>
</MudTooltip>
@if (this.SettingsManager.ConfigurationData.App.ShowAdminSettings)
{
<MudTooltip Text="@T("Export configuration")">
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Dataset" OnClick="@(() => this.ExportTranscriptionProvider(context))"/>
</MudTooltip>
}
<AdminExportButton OnClick="@(() => this.ExportTranscriptionProvider(context))" />
<MudTooltip Text="@T("Delete")">
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Delete" OnClick="@(() => this.DeleteTranscriptionProvider(context))"/>
</MudTooltip>
@@ -9,6 +9,17 @@ namespace AIStudio.Components.Settings;
public partial class SettingsPanelTranscription : SettingsPanelProviderBase
{
/// <summary>
/// Groups the table by the used LLM provider. The transcription provider list is already sorted by
/// that provider, so all instances of one LLM provider form a single, coherent group.
/// </summary>
private static readonly TableGroupDefinition<TranscriptionProvider> GROUP_CONFIG = new()
{
Expandable = true,
IsInitiallyExpanded = false,
Selector = provider => provider.UsedLLMProvider,
};
[Parameter]
public List<ConfigurationSelectData<string>> AvailableTranscriptionProviders { get; set; } = new();
@@ -60,24 +71,40 @@ public partial class SettingsPanelTranscription : SettingsPanelProviderBase
private async Task EditTranscriptionProvider(TranscriptionProvider transcriptionProvider)
{
if (transcriptionProvider.IsEnterpriseConfiguration && !transcriptionProvider.AllowUserProvidedAPIKey)
return;
var dialogParameters = new DialogParameters<TranscriptionProviderDialog>
{
{ x => x.DataNum, transcriptionProvider.Num },
{ x => x.DataId, transcriptionProvider.Id },
{ x => x.DataName, transcriptionProvider.Name },
{ x => x.DataLLMProvider, transcriptionProvider.UsedLLMProvider },
{ x => x.DataCustomIconDataUrl, transcriptionProvider.CustomIconDataUrl },
{ x => x.DataModel, transcriptionProvider.Model },
{ x => x.DataHostname, transcriptionProvider.Hostname },
{ x => x.IsSelfHosted, transcriptionProvider.IsSelfHosted },
{ x => x.IsEditing, true },
{ x => x.DataHost, transcriptionProvider.Host },
{ x => x.HFInferenceProviderId, transcriptionProvider.HFInferenceProvider },
{ x => x.IsEnterpriseConfiguration, transcriptionProvider.IsEnterpriseConfiguration },
};
var dialogReference = await this.DialogService.ShowAsync<TranscriptionProviderDialog>(T("Edit Transcription Provider"), dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result;
if (dialogResult is null || dialogResult.Canceled)
return;
if (transcriptionProvider.IsEnterpriseConfiguration)
{
// Only the API key changed, and the dialog already stored it directly. The provider
// object itself is managed by the configuration plugin and must not be overwritten
// with the dialog's copy -- doing so would let the locked-but-technically-editable
// fields drift from what the organization configured.
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
return;
}
var editedTranscriptionProvider = (TranscriptionProvider)dialogResult.Data!;
// Set the provider number if it's not set. This is important for providers
@@ -129,7 +156,7 @@ public partial class SettingsPanelTranscription : SettingsPanelProviderBase
private async Task UpdateTranscriptionProviders()
{
this.AvailableTranscriptionProviders.Clear();
foreach (var provider in this.SettingsManager.ConfigurationData.TranscriptionProviders)
foreach (var provider in this.SettingsManager.GetAllTranscriptionProviders())
this.AvailableTranscriptionProviders.Add(new (provider.Name, provider.Id));
await this.AvailableTranscriptionProvidersChanged.InvokeAsync(this.AvailableTranscriptionProviders);