mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-02-12 02:01:36 +00:00
Added settings & features for administrators in organizations (#653)
Some checks are pending
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage deb updater) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Read metadata (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage deb updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis updater) (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
Some checks are pending
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage deb updater) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Read metadata (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage deb updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis updater) (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
This commit is contained in:
parent
5af6a8db3e
commit
4cf62672de
@ -136,6 +136,8 @@ else
|
||||
|
||||
<ReadFileContent Text="@T("Load output rules from document")" @bind-FileContent="@this.policyOutputRules" Disabled="@this.IsNoPolicySelectedOrProtected"/>
|
||||
|
||||
@if (this.SettingsManager.ConfigurationData.App.ShowAdminSettings)
|
||||
{
|
||||
<MudText Typo="Typo.h5" Class="mt-6 mb-1">
|
||||
@T("Preparation for enterprise distribution")
|
||||
</MudText>
|
||||
@ -144,6 +146,7 @@ else
|
||||
@T("Export policy as configuration section")
|
||||
</MudButton>
|
||||
}
|
||||
}
|
||||
</ExpansionPanel>
|
||||
|
||||
<MudDivider Style="height: 0.25ch; margin: 1rem 0;" Class="mt-6" />
|
||||
|
||||
@ -2080,12 +2080,18 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1898060643"]
|
||||
-- Select the language for the app.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1907446663"] = "Select the language for the app."
|
||||
|
||||
-- When enabled, additional administration options become visible. These options are intended for IT staff to manage organization-wide configuration, e.g. configuring and exporting providers for an entire organization.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2013281167"] = "When enabled, additional administration options become visible. These options are intended for IT staff to manage organization-wide configuration, e.g. configuring and exporting providers for an entire organization."
|
||||
|
||||
-- The global keyboard shortcut for toggling voice recording. This shortcut works system-wide, even when the app is not focused.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2143741496"] = "The global keyboard shortcut for toggling voice recording. This shortcut works system-wide, even when the app is not focused."
|
||||
|
||||
-- Disable dictation and transcription
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T215381891"] = "Disable dictation and transcription"
|
||||
|
||||
-- Enterprise Administration
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2277116008"] = "Enterprise Administration"
|
||||
|
||||
-- Language behavior
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2341504363"] = "Language behavior"
|
||||
|
||||
@ -2095,6 +2101,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T237706157"]
|
||||
-- Language
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2591284123"] = "Language"
|
||||
|
||||
-- Administration settings are visible
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2591866808"] = "Administration settings are visible"
|
||||
|
||||
-- Save energy?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3100928009"] = "Save energy?"
|
||||
|
||||
@ -2104,9 +2113,18 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3165555978"]
|
||||
-- App Options
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3577148634"] = "App Options"
|
||||
|
||||
-- Generate a 256-bit encryption secret for encrypting API keys in configuration plugins. Deploy this secret to client machines via Group Policy (Windows Registry) or environment variables. Providers can then be exported with encrypted API keys using the export buttons in the provider settings.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T362833"] = "Generate a 256-bit encryption secret for encrypting API keys in configuration plugins. Deploy this secret to client machines via Group Policy (Windows Registry) or environment variables. Providers can then be exported with encrypted API keys using the export buttons in the provider settings."
|
||||
|
||||
-- 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.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3652888444"] = "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."
|
||||
|
||||
-- Show administration settings?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3694781396"] = "Show administration settings?"
|
||||
|
||||
-- Read the Enterprise IT documentation for details.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3705451321"] = "Read the Enterprise IT documentation for details."
|
||||
|
||||
-- Enable spellchecking?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3914529369"] = "Enable spellchecking?"
|
||||
|
||||
@ -2140,6 +2158,12 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T817101267"]
|
||||
-- Would you like to set one provider as the default for the entire app? When you configure a different provider for an assistant, it will always take precedence.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T844514734"] = "Would you like to set one provider as the default for the entire app? When you configure a different provider for an assistant, it will always take precedence."
|
||||
|
||||
-- Generate an encryption secret and copy it to the clipboard
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T922066419"] = "Generate an encryption secret and copy it to the clipboard"
|
||||
|
||||
-- Administration settings are not visible
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T929143445"] = "Administration settings are not visible"
|
||||
|
||||
-- Delete
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1469573738"] = "Delete"
|
||||
|
||||
@ -2197,6 +2221,18 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T78223
|
||||
-- Provider
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T900237532"] = "Provider"
|
||||
|
||||
-- Export configuration
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T975426229"] = "Export configuration"
|
||||
|
||||
-- Cannot export the encrypted API key: No enterprise encryption secret is configured.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERBASE::T1832230847"] = "Cannot export the encrypted API key: No enterprise encryption secret is configured."
|
||||
|
||||
-- This provider has an API key configured. Do you want to include the encrypted API key in the export? Note: The recipient will need the same encryption secret to use the API key.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERBASE::T3368145670"] = "This provider has an API key configured. Do you want to include the encrypted API key in the export? Note: The recipient will need the same encryption secret to use the API key."
|
||||
|
||||
-- Export API Key?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERBASE::T4010580285"] = "Export API Key?"
|
||||
|
||||
-- Show provider's confidence level?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1052533048"] = "Show provider's confidence level?"
|
||||
|
||||
@ -2302,6 +2338,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T853225
|
||||
-- Provider
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T900237532"] = "Provider"
|
||||
|
||||
-- Export configuration
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T975426229"] = "Export configuration"
|
||||
|
||||
-- No transcription provider configured yet.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1079350363"] = "No transcription provider configured yet."
|
||||
|
||||
@ -2356,6 +2395,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T78
|
||||
-- Provider
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T900237532"] = "Provider"
|
||||
|
||||
-- Export configuration
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T975426229"] = "Export configuration"
|
||||
|
||||
-- Copy {0} to the clipboard
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TEXTINFOLINE::T2206391442"] = "Copy {0} to the clipboard"
|
||||
|
||||
@ -5017,6 +5059,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1420062548"] = "Database version
|
||||
-- This library is used to extend the MudBlazor library. It provides additional components that are not part of the MudBlazor library.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1421513382"] = "This library is used to extend the MudBlazor library. It provides additional components that are not part of the MudBlazor library."
|
||||
|
||||
-- Encryption secret: is not configured
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1560776885"] = "Encryption secret: is not configured"
|
||||
|
||||
-- Qdrant is a vector database and vector similarity search engine. We use it to realize local RAG—retrieval-augmented generation—within AI Studio. Thanks for the effort and great work that has been and is being put into Qdrant.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1619832053"] = "Qdrant is a vector database and vector similarity search engine. We use it to realize local RAG—retrieval-augmented generation—within AI Studio. Thanks for the effort and great work that has been and is being put into Qdrant."
|
||||
|
||||
@ -5053,6 +5098,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1915240766"] = "In order to use
|
||||
-- This library is used to convert HTML to Markdown. This is necessary, e.g., when you provide a URL as input for an assistant.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1924365263"] = "This library is used to convert HTML to Markdown. This is necessary, e.g., when you provide a URL as input for an assistant."
|
||||
|
||||
-- Encryption secret: is configured
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1931141322"] = "Encryption secret: is configured"
|
||||
|
||||
-- We use Rocket to implement the runtime API. This is necessary because the runtime must be able to communicate with the user interface (IPC). Rocket is a great framework for implementing web APIs in Rust.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1943216839"] = "We use Rocket to implement the runtime API. This is necessary because the runtime must be able to communicate with the user interface (IPC). Rocket is a great framework for implementing web APIs in Rust."
|
||||
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
<ConfigurationSelect OptionDescription="@T("Check for updates")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.UpdateInterval)" Data="@ConfigurationSelectDataFactory.GetUpdateIntervalData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.UpdateInterval = selectedValue)" OptionHelp="@T("How often should we check for app updates?")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.UpdateInterval, out var meta) && meta.IsLocked"/>
|
||||
<ConfigurationSelect OptionDescription="@T("Update installation method")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.UpdateInstallation)" Data="@ConfigurationSelectDataFactory.GetUpdateBehaviourData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.UpdateInstallation = selectedValue)" OptionHelp="@T("Should updates be installed automatically or manually?")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.UpdateInstallation, out var meta) && meta.IsLocked"/>
|
||||
<ConfigurationSelect OptionDescription="@T("Navigation bar behavior")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.NavigationBehavior)" Data="@ConfigurationSelectDataFactory.GetNavBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.NavigationBehavior = selectedValue)" OptionHelp="@T("Select the desired behavior for the navigation bar.")"/>
|
||||
<ConfigurationOption OptionDescription="@T("Show administration settings?")" LabelOn="@T("Administration settings are visible")" LabelOff="@T("Administration settings are not visible")" State="@(() => this.SettingsManager.ConfigurationData.App.ShowAdminSettings)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.App.ShowAdminSettings = updatedState)" OptionHelp="@T("When enabled, additional administration options become visible. These options are intended for IT staff to manage organization-wide configuration, e.g. configuring and exporting providers for an entire organization.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.ShowAdminSettings, out var meta) && meta.IsLocked"/>
|
||||
<ConfigurationSelect OptionDescription="@T("Preview feature visibility")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.PreviewVisibility)" Data="@ConfigurationSelectDataFactory.GetPreviewVisibility()" SelectionUpdate="@this.UpdatePreviewFeatures" OptionHelp="@T("Do you want to show preview features in the app?")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.PreviewVisibility, out var meta) && meta.IsLocked"/>
|
||||
|
||||
@if (this.SettingsManager.ConfigurationData.App.PreviewVisibility > PreviewVisibility.NONE)
|
||||
@ -36,4 +37,25 @@
|
||||
<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"/>
|
||||
<ConfigurationShortcut ShortcutId="Shortcut.VOICE_RECORDING_TOGGLE" OptionDescription="@T("Voice recording shortcut")" Shortcut="@(() => this.SettingsManager.ConfigurationData.App.ShortcutVoiceRecording)" ShortcutUpdate="@(shortcut => this.SettingsManager.ConfigurationData.App.ShortcutVoiceRecording = 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"/>
|
||||
}
|
||||
|
||||
@if (this.SettingsManager.ConfigurationData.App.ShowAdminSettings)
|
||||
{
|
||||
<MudText Typo="Typo.h5" Class="mt-6 mb-3">
|
||||
@T("Enterprise Administration")
|
||||
</MudText>
|
||||
|
||||
<MudText Typo="Typo.body2" Class="mb-3">
|
||||
@T("Generate a 256-bit encryption secret for encrypting API keys in configuration plugins. Deploy this secret to client machines via Group Policy (Windows Registry) or environment variables. Providers can then be exported with encrypted API keys using the export buttons in the provider settings.")
|
||||
<MudLink Href="https://github.com/MindWorkAI/AI-Studio/blob/main/documentation/Enterprise%20IT.md" Target="_blank">
|
||||
@T("Read the Enterprise IT documentation for details.")
|
||||
</MudLink>
|
||||
</MudText>
|
||||
|
||||
<MudButton StartIcon="@Icons.Material.Filled.Key"
|
||||
Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
OnClick="@this.GenerateEncryptionSecret">
|
||||
@T("Generate an encryption secret and copy it to the clipboard")
|
||||
</MudButton>
|
||||
}
|
||||
</ExpansionPanel>
|
||||
@ -6,6 +6,12 @@ namespace AIStudio.Components.Settings;
|
||||
|
||||
public partial class SettingsPanelApp : SettingsPanelBase
|
||||
{
|
||||
private async Task GenerateEncryptionSecret()
|
||||
{
|
||||
var secret = EnterpriseEncryption.GenerateSecret();
|
||||
await this.RustService.CopyText2Clipboard(this.Snackbar, secret);
|
||||
}
|
||||
|
||||
private IEnumerable<ConfigurationSelectData<string>> GetFilteredTranscriptionProviders()
|
||||
{
|
||||
yield return new(T("Disable dictation and transcription"), string.Empty);
|
||||
|
||||
@ -15,4 +15,7 @@ public abstract class SettingsPanelBase : MSGComponentBase
|
||||
|
||||
[Inject]
|
||||
protected RustService RustService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
protected ISnackbar Snackbar { get; init; } = null!;
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
@using AIStudio.Provider
|
||||
@using AIStudio.Settings.DataModel
|
||||
@inherits SettingsPanelBase
|
||||
@inherits SettingsPanelProviderBase
|
||||
|
||||
@if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager))
|
||||
{
|
||||
@ -22,7 +22,7 @@
|
||||
<col style="width: 12em;"/>
|
||||
<col style="width: 12em;"/>
|
||||
<col/>
|
||||
<col style="width: 16em;"/>
|
||||
<col style="width: 22em;"/>
|
||||
</ColGroup>
|
||||
<HeaderContent>
|
||||
<MudTh>#</MudTh>
|
||||
@ -53,6 +53,9 @@
|
||||
<MudTooltip Text="@T("Edit")">
|
||||
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Edit" OnClick="@(() => this.EditEmbeddingProvider(context))"/>
|
||||
</MudTooltip>
|
||||
<MudTooltip Text="@T("Export configuration")">
|
||||
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Dataset" OnClick="@(() => this.ExportEmbeddingProvider(context))"/>
|
||||
</MudTooltip>
|
||||
<MudTooltip Text="@T("Delete")">
|
||||
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Delete" OnClick="@(() => this.DeleteEmbeddingProvider(context))"/>
|
||||
</MudTooltip>
|
||||
|
||||
@ -7,7 +7,7 @@ using DialogOptions = AIStudio.Dialogs.DialogOptions;
|
||||
|
||||
namespace AIStudio.Components.Settings;
|
||||
|
||||
public partial class SettingsPanelEmbeddings : SettingsPanelBase
|
||||
public partial class SettingsPanelEmbeddings : SettingsPanelProviderBase
|
||||
{
|
||||
[Parameter]
|
||||
public List<ConfigurationSelectData<string>> AvailableEmbeddingProviders { get; set; } = new();
|
||||
@ -115,6 +115,14 @@ public partial class SettingsPanelEmbeddings : SettingsPanelBase
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
|
||||
private async Task ExportEmbeddingProvider(EmbeddingProvider provider)
|
||||
{
|
||||
if (provider == EmbeddingProvider.NONE)
|
||||
return;
|
||||
|
||||
await this.ExportProvider(provider, SecretStoreType.EMBEDDING_PROVIDER, provider.ExportAsConfigurationSection);
|
||||
}
|
||||
|
||||
private async Task UpdateEmbeddingProviders()
|
||||
{
|
||||
this.AvailableEmbeddingProviders.Clear();
|
||||
|
||||
@ -0,0 +1,61 @@
|
||||
using AIStudio.Dialogs;
|
||||
using AIStudio.Tools.PluginSystem;
|
||||
|
||||
using DialogOptions = AIStudio.Dialogs.DialogOptions;
|
||||
|
||||
namespace AIStudio.Components.Settings;
|
||||
|
||||
public abstract class SettingsPanelProviderBase : SettingsPanelBase
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(SettingsPanelProviderBase).Namespace, nameof(SettingsPanelProviderBase));
|
||||
|
||||
/// <summary>
|
||||
/// Exports the provider configuration as Lua code, optionally including the encrypted API key if the provider has one
|
||||
/// configured and the user agrees to include it. The exportFunc should generate the Lua code based on the provided
|
||||
/// encrypted API key (which may be null if the user chose not to include it or if encryption is not available).
|
||||
/// The generated Lua code is then copied to the clipboard for easy sharing.
|
||||
/// </summary>
|
||||
/// <param name="secretId">The secret ID of the provider to check for an API key.</param>
|
||||
/// <param name="storeType">The type of secret store to check for the API key (e.g., LLM provider, transcription provider, etc.).</param>
|
||||
/// <param name="exportFunc">The function that generates the Lua code for the provider configuration, given the optional encrypted API key.</param>
|
||||
protected async Task ExportProvider(ISecretId secretId, SecretStoreType storeType, Func<string?, string> exportFunc)
|
||||
{
|
||||
string? encryptedApiKey = null;
|
||||
|
||||
// Check if the provider has an API key stored:
|
||||
var apiKeyResponse = await this.RustService.GetAPIKey(secretId, storeType, isTrying: true);
|
||||
if (apiKeyResponse.Success)
|
||||
{
|
||||
// Ask the user if they want to export the API key:
|
||||
var dialogParameters = new DialogParameters<ConfirmDialog>
|
||||
{
|
||||
{ x => x.Message, TB("This provider has an API key configured. Do you want to include the encrypted API key in the export? Note: The recipient will need the same encryption secret to use the API key.") },
|
||||
};
|
||||
|
||||
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>(TB("Export API Key?"), dialogParameters, DialogOptions.FULLSCREEN);
|
||||
var dialogResult = await dialogReference.Result;
|
||||
if (dialogResult is { Canceled: false })
|
||||
{
|
||||
// User wants to export the API key - encrypt it:
|
||||
var encryption = PluginFactory.EnterpriseEncryption;
|
||||
if (encryption?.IsAvailable == true)
|
||||
{
|
||||
var decryptedApiKey = await apiKeyResponse.Secret.Decrypt(Program.ENCRYPTION);
|
||||
if (encryption.TryEncrypt(decryptedApiKey, out var encrypted))
|
||||
encryptedApiKey = encrypted;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No encryption secret available - inform the user:
|
||||
this.Snackbar.Add(TB("Cannot export the encrypted API key: No enterprise encryption secret is configured."), Severity.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var luaCode = exportFunc(encryptedApiKey);
|
||||
if (string.IsNullOrWhiteSpace(luaCode))
|
||||
return;
|
||||
|
||||
await this.RustService.CopyText2Clipboard(this.Snackbar, luaCode);
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
@using AIStudio.Provider
|
||||
@using AIStudio.Settings
|
||||
@inherits SettingsPanelBase
|
||||
@inherits SettingsPanelProviderBase
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Layers" HeaderText="@T("Configure LLM Providers")">
|
||||
<MudText Typo="Typo.h4" Class="mb-3">
|
||||
@ -15,7 +15,7 @@
|
||||
<col style="width: 12em;"/>
|
||||
<col style="width: 12em;"/>
|
||||
<col/>
|
||||
<col style="width: 16em;"/>
|
||||
<col style="width: 22em;"/>
|
||||
</ColGroup>
|
||||
<HeaderContent>
|
||||
<MudTh>#</MudTh>
|
||||
@ -45,6 +45,9 @@
|
||||
<MudTooltip Text="@T("Edit")">
|
||||
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Edit" OnClick="@(() => this.EditLLMProvider(context))"/>
|
||||
</MudTooltip>
|
||||
<MudTooltip Text="@T("Export configuration")">
|
||||
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Dataset" OnClick="@(() => this.ExportLLMProvider(context))"/>
|
||||
</MudTooltip>
|
||||
<MudTooltip Text="@T("Delete")">
|
||||
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Delete" OnClick="@(() => this.DeleteLLMProvider(context))"/>
|
||||
</MudTooltip>
|
||||
|
||||
@ -10,7 +10,7 @@ using DialogOptions = AIStudio.Dialogs.DialogOptions;
|
||||
|
||||
namespace AIStudio.Components.Settings;
|
||||
|
||||
public partial class SettingsPanelProviders : SettingsPanelBase
|
||||
public partial class SettingsPanelProviders : SettingsPanelProviderBase
|
||||
{
|
||||
[Parameter]
|
||||
public List<ConfigurationSelectData<string>> AvailableLLMProviders { get; set; } = new();
|
||||
@ -134,6 +134,14 @@ public partial class SettingsPanelProviders : SettingsPanelBase
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
|
||||
private async Task ExportLLMProvider(AIStudio.Settings.Provider provider)
|
||||
{
|
||||
if (provider == AIStudio.Settings.Provider.NONE)
|
||||
return;
|
||||
|
||||
await this.ExportProvider(provider, SecretStoreType.LLM_PROVIDER, provider.ExportAsConfigurationSection);
|
||||
}
|
||||
|
||||
private string GetLLMProviderModelName(AIStudio.Settings.Provider provider)
|
||||
{
|
||||
// For system models, return localized text:
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
@using AIStudio.Provider
|
||||
@using AIStudio.Settings.DataModel
|
||||
@inherits SettingsPanelBase
|
||||
@inherits SettingsPanelProviderBase
|
||||
|
||||
@if (PreviewFeatures.PRE_SPEECH_TO_TEXT_2026.IsEnabled(this.SettingsManager))
|
||||
{
|
||||
@ -19,7 +19,7 @@
|
||||
<col style="width: 12em;"/>
|
||||
<col style="width: 12em;"/>
|
||||
<col/>
|
||||
<col style="width: 16em;"/>
|
||||
<col style="width: 22em;"/>
|
||||
</ColGroup>
|
||||
<HeaderContent>
|
||||
<MudTh>#</MudTh>
|
||||
@ -50,6 +50,9 @@
|
||||
<MudTooltip Text="@T("Edit")">
|
||||
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Edit" OnClick="@(() => this.EditTranscriptionProvider(context))"/>
|
||||
</MudTooltip>
|
||||
<MudTooltip Text="@T("Export configuration")">
|
||||
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Dataset" OnClick="@(() => this.ExportTranscriptionProvider(context))"/>
|
||||
</MudTooltip>
|
||||
<MudTooltip Text="@T("Delete")">
|
||||
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Delete" OnClick="@(() => this.DeleteTranscriptionProvider(context))"/>
|
||||
</MudTooltip>
|
||||
|
||||
@ -7,7 +7,7 @@ using DialogOptions = AIStudio.Dialogs.DialogOptions;
|
||||
|
||||
namespace AIStudio.Components.Settings;
|
||||
|
||||
public partial class SettingsPanelTranscription : SettingsPanelBase
|
||||
public partial class SettingsPanelTranscription : SettingsPanelProviderBase
|
||||
{
|
||||
[Parameter]
|
||||
public List<ConfigurationSelectData<string>> AvailableTranscriptionProviders { get; set; } = new();
|
||||
@ -115,6 +115,14 @@ public partial class SettingsPanelTranscription : SettingsPanelBase
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
|
||||
private async Task ExportTranscriptionProvider(TranscriptionProvider provider)
|
||||
{
|
||||
if (provider == TranscriptionProvider.NONE)
|
||||
return;
|
||||
|
||||
await this.ExportProvider(provider, SecretStoreType.TRANSCRIPTION_PROVIDER, provider.ExportAsConfigurationSection);
|
||||
}
|
||||
|
||||
private async Task UpdateTranscriptionProviders()
|
||||
{
|
||||
this.AvailableTranscriptionProviders.Clear();
|
||||
|
||||
@ -215,6 +215,9 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
|
||||
if (enterpriseEnvironment != default)
|
||||
await PluginFactory.TryDownloadingConfigPluginAsync(enterpriseEnvironment.ConfigurationId, enterpriseEnvironment.ConfigurationServerUrl);
|
||||
|
||||
// Initialize the enterprise encryption service for decrypting API keys:
|
||||
await PluginFactory.InitializeEnterpriseEncryption(this.RustService);
|
||||
|
||||
// Load (but not start) all plugins without waiting for them:
|
||||
#if DEBUG
|
||||
var pluginLoadingTimeout = new CancellationTokenSource();
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
@attribute [Route(Routes.ABOUT)]
|
||||
@using AIStudio.Tools.PluginSystem
|
||||
@using AIStudio.Tools.Services
|
||||
@inherits MSGComponentBase
|
||||
|
||||
@ -68,6 +69,21 @@
|
||||
<MudCopyClipboardButton TooltipMessage="@T("Copies the configuration plugin ID to the clipboard")" StringContent=@this.configPlug!.Id.ToString()/>
|
||||
</div>
|
||||
</MudText>
|
||||
<MudText Typo="Typo.body1" Class="mt-2 mb-2">
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<MudIcon Icon="@Icons.Material.Filled.ArrowRightAlt"/>
|
||||
@if (PluginFactory.EnterpriseEncryption?.IsAvailable is true)
|
||||
{
|
||||
<MudIcon Icon="@Icons.Material.Filled.CheckCircle" Color="Color.Success" Size="Size.Small"/>
|
||||
<span>@T("Encryption secret: is configured")</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudIcon Icon="@Icons.Material.Filled.Cancel" Color="Color.Error" Size="Size.Small"/>
|
||||
<span>@T("Encryption secret: is not configured")</span>
|
||||
}
|
||||
</div>
|
||||
</MudText>
|
||||
</MudCollapse>
|
||||
break;
|
||||
|
||||
@ -91,6 +107,21 @@
|
||||
<MudCopyClipboardButton TooltipMessage="@T("Copies the server URL to the clipboard")" StringContent=@EnterpriseEnvironmentService.CURRENT_ENVIRONMENT.ConfigurationServerUrl/>
|
||||
</div>
|
||||
</MudText>
|
||||
<MudText Typo="Typo.body1" Class="mt-2 mb-2">
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<MudIcon Icon="@Icons.Material.Filled.ArrowRightAlt"/>
|
||||
@if (PluginFactory.EnterpriseEncryption?.IsAvailable is true)
|
||||
{
|
||||
<MudIcon Icon="@Icons.Material.Filled.CheckCircle" Color="Color.Success" Size="Size.Small"/>
|
||||
<span>@T("Encryption secret: is configured")</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudIcon Icon="@Icons.Material.Filled.Cancel" Color="Color.Error" Size="Size.Small"/>
|
||||
<span>@T("Encryption secret: is not configured")</span>
|
||||
}
|
||||
</div>
|
||||
</MudText>
|
||||
</MudCollapse>
|
||||
break;
|
||||
|
||||
@ -122,6 +153,21 @@
|
||||
<MudCopyClipboardButton TooltipMessage="@T("Copies the configuration plugin ID to the clipboard")" StringContent=@this.configPlug!.Id.ToString()/>
|
||||
</div>
|
||||
</MudText>
|
||||
<MudText Typo="Typo.body1" Class="mt-2 mb-2">
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<MudIcon Icon="@Icons.Material.Filled.ArrowRightAlt"/>
|
||||
@if (PluginFactory.EnterpriseEncryption?.IsAvailable is true)
|
||||
{
|
||||
<MudIcon Icon="@Icons.Material.Filled.CheckCircle" Color="Color.Success" Size="Size.Small"/>
|
||||
<span>@T("Encryption secret: is configured")</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudIcon Icon="@Icons.Material.Filled.Cancel" Color="Color.Error" Size="Size.Small"/>
|
||||
<span>@T("Encryption secret: is not configured")</span>
|
||||
}
|
||||
</div>
|
||||
</MudText>
|
||||
</MudCollapse>
|
||||
break;
|
||||
}
|
||||
|
||||
@ -64,6 +64,20 @@ CONFIG["LLM_PROVIDERS"] = {}
|
||||
-- -- Could be something like ... \"temperature\": 0.5, \"max_tokens\": 1000 ... for multiple parameters.
|
||||
-- -- Please do not add the enclosing curly braces {} here. Also, no trailing comma is allowed.
|
||||
-- ["AdditionalJsonApiParameters"] = "",
|
||||
--
|
||||
-- -- Optional: Hugging Face inference provider. Only relevant for UsedLLMProvider = HUGGINGFACE.
|
||||
-- -- Allowed values are: CEREBRAS, NEBIUS_AI_STUDIO, SAMBANOVA, NOVITA, HYPERBOLIC, TOGETHER_AI, FIREWORKS, HF_INFERENCE_API
|
||||
-- -- ["HFInferenceProvider"] = "NOVITA",
|
||||
--
|
||||
-- -- Optional: Encrypted API key for cloud providers or secured on-premise models.
|
||||
-- -- The API key must be encrypted using the enterprise encryption secret.
|
||||
-- -- Format: "ENC:v1:<base64-encoded encrypted data>"
|
||||
-- -- The encryption secret must be configured via:
|
||||
-- -- Windows Registry: HKEY_CURRENT_USER\Software\github\MindWork AI Studio\Enterprise IT\config_encryption_secret
|
||||
-- -- Environment variable: MINDWORK_AI_STUDIO_ENTERPRISE_CONFIG_ENCRYPTION_SECRET
|
||||
-- -- You can export an encrypted API key from an existing provider using the export button in the settings.
|
||||
-- -- ["APIKey"] = "ENC:v1:<base64-encoded encrypted data>",
|
||||
--
|
||||
-- ["Model"] = {
|
||||
-- ["Id"] = "<the model ID>",
|
||||
-- ["DisplayName"] = "<user-friendly name of the model>",
|
||||
@ -82,6 +96,10 @@ CONFIG["TRANSCRIPTION_PROVIDERS"] = {}
|
||||
-- -- Allowed values for Host are: LM_STUDIO, LLAMACPP, OLLAMA, VLLM, and WHISPER_CPP
|
||||
-- ["Host"] = "WHISPER_CPP",
|
||||
-- ["Hostname"] = "<https address of the server>",
|
||||
--
|
||||
-- -- Optional: Encrypted API key (see LLM_PROVIDERS example for details)
|
||||
-- -- ["APIKey"] = "ENC:v1:<base64-encoded encrypted data>",
|
||||
--
|
||||
-- ["Model"] = {
|
||||
-- ["Id"] = "<the model ID>",
|
||||
-- ["DisplayName"] = "<user-friendly name of the model>",
|
||||
@ -100,6 +118,10 @@ CONFIG["EMBEDDING_PROVIDERS"] = {}
|
||||
-- -- Allowed values for Host are: LM_STUDIO, LLAMACPP, OLLAMA, and VLLM
|
||||
-- ["Host"] = "OLLAMA",
|
||||
-- ["Hostname"] = "<https address of the server>",
|
||||
--
|
||||
-- -- Optional: Encrypted API key (see LLM_PROVIDERS example for details)
|
||||
-- -- ["APIKey"] = "ENC:v1:<base64-encoded encrypted data>",
|
||||
--
|
||||
-- ["Model"] = {
|
||||
-- ["Id"] = "<the model ID, e.g., nomic-embed-text>",
|
||||
-- ["DisplayName"] = "<user-friendly name of the model>",
|
||||
@ -120,6 +142,10 @@ CONFIG["SETTINGS"] = {}
|
||||
-- Allowed values are: true, false
|
||||
-- CONFIG["SETTINGS"]["DataApp.AllowUserToAddProvider"] = false
|
||||
|
||||
-- Configure whether administration settings are visible in the UI:
|
||||
-- Allowed values are: true, false
|
||||
-- CONFIG["SETTINGS"]["DataApp.ShowAdminSettings"] = true
|
||||
|
||||
-- Configure the visibility of preview features:
|
||||
-- Allowed values are: NONE, RELEASE_CANDIDATE, BETA, ALPHA, PROTOTYPE, EXPERIMENTAL
|
||||
-- Please note:
|
||||
|
||||
@ -2082,12 +2082,18 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1898060643"]
|
||||
-- Select the language for the app.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1907446663"] = "Wählen Sie die Sprache für die App aus."
|
||||
|
||||
-- When enabled, additional administration options become visible. These options are intended for IT staff to manage organization-wide configuration, e.g. configuring and exporting providers for an entire organization.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2013281167"] = "Wenn diese Option aktiviert ist, werden zusätzliche Optionen für die Administration angezeigt. Diese Optionen sind für IT-Mitarbeitende vorgesehen, um organisationsweite Einstellungen zu verwalten, z. B. Anbieter für eine gesamte Organisation zu konfigurieren und zu exportieren."
|
||||
|
||||
-- The global keyboard shortcut for toggling voice recording. This shortcut works system-wide, even when the app is not focused.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2143741496"] = "Der globale Tastaturkurzbefehl zum Ein- und Ausschalten der Sprachaufnahme. Dieser Kurzbefehl funktioniert systemweit, auch wenn die App nicht im Vordergrund ist."
|
||||
|
||||
-- Disable dictation and transcription
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T215381891"] = "Diktieren und Transkribieren deaktivieren"
|
||||
|
||||
-- Enterprise Administration
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2277116008"] = "Unternehmensverwaltung"
|
||||
|
||||
-- Language behavior
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2341504363"] = "Sprachverhalten"
|
||||
|
||||
@ -2097,6 +2103,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T237706157"]
|
||||
-- Language
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2591284123"] = "Sprache"
|
||||
|
||||
-- Administration settings are visible
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2591866808"] = "Die Optionen für die Administration sind sichtbar."
|
||||
|
||||
-- Save energy?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3100928009"] = "Energie sparen?"
|
||||
|
||||
@ -2106,9 +2115,18 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3165555978"]
|
||||
-- App Options
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3577148634"] = "App-Einstellungen"
|
||||
|
||||
-- Generate a 256-bit encryption secret for encrypting API keys in configuration plugins. Deploy this secret to client machines via Group Policy (Windows Registry) or environment variables. Providers can then be exported with encrypted API keys using the export buttons in the provider settings.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T362833"] = "Generieren Sie ein 256‑Bit‑Geheimnis für die Verschlüsselung, um API‑Schlüssel in Konfigurations-Plugins zu verschlüsseln. Stellen Sie dieses Geheimnis über Gruppenrichtlinien (Windows-Registrierung) oder über Umgebungsvariablen auf Client-Geräten bereit. Anschließend können Anbieter über die Export-Schaltflächen in den Anbieter-Einstellungen mit verschlüsselten API‑Schlüsseln exportiert werden."
|
||||
|
||||
-- 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.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3652888444"] = "Wenn aktiviert, wird gestreamter Inhalt von der KI alle drei Sekunden aktualisiert. Wenn deaktiviert, wird gestreamter Inhalt sofort aktualisiert, sobald er verfügbar ist."
|
||||
|
||||
-- Show administration settings?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3694781396"] = "Optionen für die Administration anzeigen?"
|
||||
|
||||
-- Read the Enterprise IT documentation for details.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3705451321"] = "Lesen Sie die Enterprise-IT-Dokumentation für die Details."
|
||||
|
||||
-- Enable spellchecking?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3914529369"] = "Rechtschreibprüfung aktivieren?"
|
||||
|
||||
@ -2142,6 +2160,12 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T817101267"]
|
||||
-- Would you like to set one provider as the default for the entire app? When you configure a different provider for an assistant, it will always take precedence.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T844514734"] = "Möchten Sie einen Anbieter als Standard für die gesamte App festlegen? Wenn Sie einen anderen Anbieter für einen Assistenten konfigurieren, hat dieser immer Vorrang."
|
||||
|
||||
-- Generate an encryption secret and copy it to the clipboard
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T922066419"] = "Geheimnis für die Verschlüsselung generieren und in die Zwischenablage kopieren"
|
||||
|
||||
-- Administration settings are not visible
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T929143445"] = "Die Optionen für die Administration sind nicht sichtbar."
|
||||
|
||||
-- Delete
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1469573738"] = "Löschen"
|
||||
|
||||
@ -2199,6 +2223,18 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T78223
|
||||
-- Provider
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T900237532"] = "Anbieter"
|
||||
|
||||
-- Export configuration
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T975426229"] = "Konfiguration exportieren"
|
||||
|
||||
-- Cannot export the encrypted API key: No enterprise encryption secret is configured.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERBASE::T1832230847"] = "Der verschlüsselte API-Schlüssel kann nicht exportiert werden: Es ist kein Geheimnis für die Verschlüsselung konfiguriert."
|
||||
|
||||
-- This provider has an API key configured. Do you want to include the encrypted API key in the export? Note: The recipient will need the same encryption secret to use the API key.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERBASE::T3368145670"] = "Für diesen Anbieter ist ein API-Schlüssel konfiguriert. Möchten Sie den verschlüsselten API-Schlüssel in den Export aufnehmen? Hinweis: Der Empfänger benötigt dasselbe Geheimnis für die Verschlüsselung, um den API-Schlüssel verwenden zu können."
|
||||
|
||||
-- Export API Key?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERBASE::T4010580285"] = "API-Schlüssel exportieren?"
|
||||
|
||||
-- Show provider's confidence level?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1052533048"] = "Anzeigen, wie sicher sich der Anbieter ist?"
|
||||
|
||||
@ -2304,6 +2340,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T853225
|
||||
-- Provider
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T900237532"] = "Anbieter"
|
||||
|
||||
-- Export configuration
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T975426229"] = "Konfiguration exportieren"
|
||||
|
||||
-- No transcription provider configured yet.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1079350363"] = "Es ist bisher kein Anbieter für Transkriptionen konfiguriert."
|
||||
|
||||
@ -2358,6 +2397,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T78
|
||||
-- Provider
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T900237532"] = "Anbieter"
|
||||
|
||||
-- Export configuration
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T975426229"] = "Konfiguration exportieren"
|
||||
|
||||
-- Copy {0} to the clipboard
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TEXTINFOLINE::T2206391442"] = "Kopiere {0} in die Zwischenablage"
|
||||
|
||||
@ -5019,7 +5061,10 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1420062548"] = "Datenbankversion
|
||||
-- This library is used to extend the MudBlazor library. It provides additional components that are not part of the MudBlazor library.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1421513382"] = "Diese Bibliothek wird verwendet, um die MudBlazor-Bibliothek zu erweitern. Sie stellt zusätzliche Komponenten bereit, die nicht Teil der MudBlazor-Bibliothek sind."
|
||||
|
||||
-- Qdrant is a vector database and vector similarity search engine. We use it to realize local RAG—retrieval-augmented generation—within AI Studio. Thanks for the effort and great work that has been and is being put into Qdrant.
|
||||
-- Encryption secret: is not configured
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1560776885"] = "Geheimnis für die Verschlüsselung: ist nicht konfiguriert"
|
||||
|
||||
-- Qdrant is a vector database and vector similarity search engine. We use it to realize local RAG -— retrieval-augmented generation -— within AI Studio. Thanks for the effort and great work that has been and is being put into Qdrant.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1619832053"] = "Qdrant ist eine Vektordatenbank und Suchmaschine für Vektoren. Wir nutzen Qdrant, um lokales RAG (Retrieval-Augmented Generation) innerhalb von AI Studio zu realisieren. Vielen Dank für den Einsatz und die großartige Arbeit, die in Qdrant gesteckt wurde und weiterhin gesteckt wird."
|
||||
|
||||
-- We use Lua as the language for plugins. Lua-CSharp lets Lua scripts communicate with AI Studio and vice versa. Thank you, Yusuke Nakada, for this great library.
|
||||
@ -5055,6 +5100,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1915240766"] = "Um ein beliebige
|
||||
-- This library is used to convert HTML to Markdown. This is necessary, e.g., when you provide a URL as input for an assistant.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1924365263"] = "Diese Bibliothek wird verwendet, um HTML in Markdown umzuwandeln. Das ist zum Beispiel notwendig, wenn Sie eine URL als Eingabe für einen Assistenten angeben."
|
||||
|
||||
-- Encryption secret: is configured
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1931141322"] = "Geheimnis für die Verschlüsselung: ist konfiguriert"
|
||||
|
||||
-- We use Rocket to implement the runtime API. This is necessary because the runtime must be able to communicate with the user interface (IPC). Rocket is a great framework for implementing web APIs in Rust.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1943216839"] = "Wir verwenden Rocket zur Implementierung der Runtime-API. Dies ist notwendig, da die Runtime mit der Benutzeroberfläche (IPC) kommunizieren muss. Rocket ist ein ausgezeichnetes Framework zur Umsetzung von Web-APIs in Rust."
|
||||
|
||||
|
||||
@ -2082,12 +2082,18 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1898060643"]
|
||||
-- Select the language for the app.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1907446663"] = "Select the language for the app."
|
||||
|
||||
-- When enabled, additional administration options become visible. These options are intended for IT staff to manage organization-wide configuration, e.g. configuring and exporting providers for an entire organization.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2013281167"] = "When enabled, additional administration options become visible. These options are intended for IT staff to manage organization-wide configuration, e.g. configuring and exporting providers for an entire organization."
|
||||
|
||||
-- The global keyboard shortcut for toggling voice recording. This shortcut works system-wide, even when the app is not focused.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2143741496"] = "The global keyboard shortcut for toggling voice recording. This shortcut works system-wide, even when the app is not focused."
|
||||
|
||||
-- Disable dictation and transcription
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T215381891"] = "Disable dictation and transcription"
|
||||
|
||||
-- Enterprise Administration
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2277116008"] = "Enterprise Administration"
|
||||
|
||||
-- Language behavior
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2341504363"] = "Language behavior"
|
||||
|
||||
@ -2097,6 +2103,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T237706157"]
|
||||
-- Language
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2591284123"] = "Language"
|
||||
|
||||
-- Administration settings are visible
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2591866808"] = "Administration settings are visible"
|
||||
|
||||
-- Save energy?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3100928009"] = "Save energy?"
|
||||
|
||||
@ -2106,9 +2115,18 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3165555978"]
|
||||
-- App Options
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3577148634"] = "App Options"
|
||||
|
||||
-- Generate a 256-bit encryption secret for encrypting API keys in configuration plugins. Deploy this secret to client machines via Group Policy (Windows Registry) or environment variables. Providers can then be exported with encrypted API keys using the export buttons in the provider settings.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T362833"] = "Generate a 256-bit encryption secret for encrypting API keys in configuration plugins. Deploy this secret to client machines via Group Policy (Windows Registry) or environment variables. Providers can then be exported with encrypted API keys using the export buttons in the provider settings."
|
||||
|
||||
-- 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.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3652888444"] = "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."
|
||||
|
||||
-- Show administration settings?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3694781396"] = "Show administration settings?"
|
||||
|
||||
-- Read the Enterprise IT documentation for details.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3705451321"] = "Read the Enterprise IT documentation for details."
|
||||
|
||||
-- Enable spellchecking?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3914529369"] = "Enable spellchecking?"
|
||||
|
||||
@ -2142,6 +2160,12 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T817101267"]
|
||||
-- Would you like to set one provider as the default for the entire app? When you configure a different provider for an assistant, it will always take precedence.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T844514734"] = "Would you like to set one provider as the default for the entire app? When you configure a different provider for an assistant, it will always take precedence."
|
||||
|
||||
-- Generate an encryption secret and copy it to the clipboard
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T922066419"] = "Generate an encryption secret and copy it to the clipboard"
|
||||
|
||||
-- Administration settings are not visible
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T929143445"] = "Administration settings are not visible"
|
||||
|
||||
-- Delete
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1469573738"] = "Delete"
|
||||
|
||||
@ -2199,6 +2223,18 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T78223
|
||||
-- Provider
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T900237532"] = "Provider"
|
||||
|
||||
-- Export configuration
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T975426229"] = "Export configuration"
|
||||
|
||||
-- Cannot export the encrypted API key: No enterprise encryption secret is configured.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERBASE::T1832230847"] = "Cannot export the encrypted API key: No enterprise encryption secret is configured."
|
||||
|
||||
-- This provider has an API key configured. Do you want to include the encrypted API key in the export? Note: The recipient will need the same encryption secret to use the API key.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERBASE::T3368145670"] = "This provider has an API key configured. Do you want to include the encrypted API key in the export? Note: The recipient will need the same encryption secret to use the API key."
|
||||
|
||||
-- Export API Key?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERBASE::T4010580285"] = "Export API Key?"
|
||||
|
||||
-- Show provider's confidence level?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1052533048"] = "Show provider's confidence level?"
|
||||
|
||||
@ -2304,6 +2340,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T853225
|
||||
-- Provider
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T900237532"] = "Provider"
|
||||
|
||||
-- Export configuration
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T975426229"] = "Export configuration"
|
||||
|
||||
-- No transcription provider configured yet.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1079350363"] = "No transcription provider configured yet."
|
||||
|
||||
@ -2358,6 +2397,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T78
|
||||
-- Provider
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T900237532"] = "Provider"
|
||||
|
||||
-- Export configuration
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T975426229"] = "Export configuration"
|
||||
|
||||
-- Copy {0} to the clipboard
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TEXTINFOLINE::T2206391442"] = "Copy {0} to the clipboard"
|
||||
|
||||
@ -5019,7 +5061,10 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1420062548"] = "Database version
|
||||
-- This library is used to extend the MudBlazor library. It provides additional components that are not part of the MudBlazor library.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1421513382"] = "This library is used to extend the MudBlazor library. It provides additional components that are not part of the MudBlazor library."
|
||||
|
||||
-- Qdrant is a vector database and vector similarity search engine. We use it to realize local RAG—retrieval-augmented generation—within AI Studio. Thanks for the effort and great work that has been and is being put into Qdrant.
|
||||
-- Encryption secret: is not configured
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1560776885"] = "Encryption secret: is not configured"
|
||||
|
||||
-- Qdrant is a vector database and vector similarity search engine. We use it to realize local RAG -— retrieval-augmented generation -— within AI Studio. Thanks for the effort and great work that has been and is being put into Qdrant.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1619832053"] = "Qdrant is a vector database and vector similarity search engine. We use it to realize local RAG -— retrieval-augmented generation -— within AI Studio. Thanks for the effort and great work that has been and is being put into Qdrant."
|
||||
|
||||
-- We use Lua as the language for plugins. Lua-CSharp lets Lua scripts communicate with AI Studio and vice versa. Thank you, Yusuke Nakada, for this great library.
|
||||
@ -5055,6 +5100,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1915240766"] = "In order to use
|
||||
-- This library is used to convert HTML to Markdown. This is necessary, e.g., when you provide a URL as input for an assistant.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1924365263"] = "This library is used to convert HTML to Markdown. This is necessary, e.g., when you provide a URL as input for an assistant."
|
||||
|
||||
-- Encryption secret: is configured
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1931141322"] = "Encryption secret: is configured"
|
||||
|
||||
-- We use Rocket to implement the runtime API. This is necessary because the runtime must be able to communicate with the user interface (IPC). Rocket is a great framework for implementing web APIs in Rust.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1943216839"] = "We use Rocket to implement the runtime API. This is necessary because the runtime must be able to communicate with the user interface (IPC). Rocket is a great framework for implementing web APIs in Rust."
|
||||
|
||||
|
||||
@ -112,9 +112,14 @@ public abstract class BaseProvider : IProvider, ISecretId
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Whether this provider was imported from an enterprise configuration plugin.
|
||||
/// </summary>
|
||||
public bool IsEnterpriseConfiguration { get; init; }
|
||||
|
||||
#region Implementation of ISecretId
|
||||
|
||||
public string SecretId => this.Id;
|
||||
public string SecretId => this.IsEnterpriseConfiguration ? $"{ISecretId.ENTERPRISE_KEY_PREFIX}::{this.Id}" : this.Id;
|
||||
|
||||
public string SecretName => this.InstanceName;
|
||||
|
||||
|
||||
@ -186,7 +186,7 @@ public static class LLMProvidersExtensions
|
||||
/// <returns>The provider instance.</returns>
|
||||
public static IProvider CreateProvider(this AIStudio.Settings.Provider providerSettings)
|
||||
{
|
||||
return providerSettings.UsedLLMProvider.CreateProvider(providerSettings.InstanceName, providerSettings.Host, providerSettings.Hostname, providerSettings.Model, providerSettings.HFInferenceProvider, providerSettings.AdditionalJsonApiParameters);
|
||||
return providerSettings.UsedLLMProvider.CreateProvider(providerSettings.InstanceName, providerSettings.Host, providerSettings.Hostname, providerSettings.Model, providerSettings.HFInferenceProvider, providerSettings.AdditionalJsonApiParameters, providerSettings.IsEnterpriseConfiguration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -196,7 +196,7 @@ public static class LLMProvidersExtensions
|
||||
/// <returns>The provider instance.</returns>
|
||||
public static IProvider CreateProvider(this EmbeddingProvider embeddingProviderSettings)
|
||||
{
|
||||
return embeddingProviderSettings.UsedLLMProvider.CreateProvider(embeddingProviderSettings.Name, embeddingProviderSettings.Host, embeddingProviderSettings.Hostname, embeddingProviderSettings.Model, HFInferenceProvider.NONE);
|
||||
return embeddingProviderSettings.UsedLLMProvider.CreateProvider(embeddingProviderSettings.Name, embeddingProviderSettings.Host, embeddingProviderSettings.Hostname, embeddingProviderSettings.Model, HFInferenceProvider.NONE, isEnterpriseConfiguration: embeddingProviderSettings.IsEnterpriseConfiguration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -206,33 +206,33 @@ public static class LLMProvidersExtensions
|
||||
/// <returns>The provider instance.</returns>
|
||||
public static IProvider CreateProvider(this TranscriptionProvider transcriptionProviderSettings)
|
||||
{
|
||||
return transcriptionProviderSettings.UsedLLMProvider.CreateProvider(transcriptionProviderSettings.Name, transcriptionProviderSettings.Host, transcriptionProviderSettings.Hostname, transcriptionProviderSettings.Model, HFInferenceProvider.NONE);
|
||||
return transcriptionProviderSettings.UsedLLMProvider.CreateProvider(transcriptionProviderSettings.Name, transcriptionProviderSettings.Host, transcriptionProviderSettings.Hostname, transcriptionProviderSettings.Model, HFInferenceProvider.NONE, isEnterpriseConfiguration: transcriptionProviderSettings.IsEnterpriseConfiguration);
|
||||
}
|
||||
|
||||
private static IProvider CreateProvider(this LLMProviders provider, string instanceName, Host host, string hostname, Model model, HFInferenceProvider inferenceProvider, string expertProviderApiParameter = "")
|
||||
private static IProvider CreateProvider(this LLMProviders provider, string instanceName, Host host, string hostname, Model model, HFInferenceProvider inferenceProvider, string expertProviderApiParameter = "", bool isEnterpriseConfiguration = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
return provider switch
|
||||
{
|
||||
LLMProviders.OPEN_AI => new ProviderOpenAI { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter },
|
||||
LLMProviders.ANTHROPIC => new ProviderAnthropic { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter },
|
||||
LLMProviders.MISTRAL => new ProviderMistral { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter },
|
||||
LLMProviders.GOOGLE => new ProviderGoogle { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter },
|
||||
LLMProviders.X => new ProviderX { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter },
|
||||
LLMProviders.DEEP_SEEK => new ProviderDeepSeek { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter },
|
||||
LLMProviders.ALIBABA_CLOUD => new ProviderAlibabaCloud { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter },
|
||||
LLMProviders.PERPLEXITY => new ProviderPerplexity { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter },
|
||||
LLMProviders.OPEN_ROUTER => new ProviderOpenRouter { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter },
|
||||
LLMProviders.OPEN_AI => new ProviderOpenAI { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter, IsEnterpriseConfiguration = isEnterpriseConfiguration },
|
||||
LLMProviders.ANTHROPIC => new ProviderAnthropic { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter, IsEnterpriseConfiguration = isEnterpriseConfiguration },
|
||||
LLMProviders.MISTRAL => new ProviderMistral { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter, IsEnterpriseConfiguration = isEnterpriseConfiguration },
|
||||
LLMProviders.GOOGLE => new ProviderGoogle { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter, IsEnterpriseConfiguration = isEnterpriseConfiguration },
|
||||
LLMProviders.X => new ProviderX { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter, IsEnterpriseConfiguration = isEnterpriseConfiguration },
|
||||
LLMProviders.DEEP_SEEK => new ProviderDeepSeek { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter, IsEnterpriseConfiguration = isEnterpriseConfiguration },
|
||||
LLMProviders.ALIBABA_CLOUD => new ProviderAlibabaCloud { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter, IsEnterpriseConfiguration = isEnterpriseConfiguration },
|
||||
LLMProviders.PERPLEXITY => new ProviderPerplexity { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter, IsEnterpriseConfiguration = isEnterpriseConfiguration },
|
||||
LLMProviders.OPEN_ROUTER => new ProviderOpenRouter { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter, IsEnterpriseConfiguration = isEnterpriseConfiguration },
|
||||
|
||||
LLMProviders.GROQ => new ProviderGroq { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter },
|
||||
LLMProviders.FIREWORKS => new ProviderFireworks { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter },
|
||||
LLMProviders.HUGGINGFACE => new ProviderHuggingFace(inferenceProvider, model) { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter },
|
||||
LLMProviders.GROQ => new ProviderGroq { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter, IsEnterpriseConfiguration = isEnterpriseConfiguration },
|
||||
LLMProviders.FIREWORKS => new ProviderFireworks { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter, IsEnterpriseConfiguration = isEnterpriseConfiguration },
|
||||
LLMProviders.HUGGINGFACE => new ProviderHuggingFace(inferenceProvider, model) { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter, IsEnterpriseConfiguration = isEnterpriseConfiguration },
|
||||
|
||||
LLMProviders.SELF_HOSTED => new ProviderSelfHosted(host, hostname) { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter },
|
||||
LLMProviders.SELF_HOSTED => new ProviderSelfHosted(host, hostname) { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter, IsEnterpriseConfiguration = isEnterpriseConfiguration },
|
||||
|
||||
LLMProviders.HELMHOLTZ => new ProviderHelmholtz { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter },
|
||||
LLMProviders.GWDG => new ProviderGWDG { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter },
|
||||
LLMProviders.HELMHOLTZ => new ProviderHelmholtz { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter, IsEnterpriseConfiguration = isEnterpriseConfiguration },
|
||||
LLMProviders.GWDG => new ProviderGWDG { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter, IsEnterpriseConfiguration = isEnterpriseConfiguration },
|
||||
|
||||
_ => new NoProvider(),
|
||||
};
|
||||
|
||||
@ -94,6 +94,11 @@ public sealed class DataApp(Expression<Func<Data, DataApp>>? configSelection = n
|
||||
/// </summary>
|
||||
public bool AllowUserToAddProvider { get; set; } = ManagedConfiguration.Register(configSelection, n => n.AllowUserToAddProvider, true);
|
||||
|
||||
/// <summary>
|
||||
/// Should administration settings be visible in the UI?
|
||||
/// </summary>
|
||||
public bool ShowAdminSettings { get; set; } = ManagedConfiguration.Register(configSelection, n => n.ShowAdminSettings, false);
|
||||
|
||||
/// <summary>
|
||||
/// List of assistants that should be hidden from the UI.
|
||||
/// </summary>
|
||||
|
||||
@ -43,7 +43,7 @@ public sealed record EmbeddingProvider(
|
||||
|
||||
/// <inheritdoc />
|
||||
[JsonIgnore]
|
||||
public string SecretId => this.Id;
|
||||
public string SecretId => this.IsEnterpriseConfiguration ? $"{ISecretId.ENTERPRISE_KEY_PREFIX}::{this.UsedLLMProvider.ToName()}" : this.UsedLLMProvider.ToName();
|
||||
|
||||
/// <inheritdoc />
|
||||
[JsonIgnore]
|
||||
@ -110,6 +110,34 @@ public sealed record EmbeddingProvider(
|
||||
Host = host,
|
||||
};
|
||||
|
||||
// Handle encrypted API key if present:
|
||||
if (table.TryGetValue("APIKey", out var apiKeyValue) && apiKeyValue.TryRead<string>(out var apiKeyText) && !string.IsNullOrWhiteSpace(apiKeyText))
|
||||
{
|
||||
if (!EnterpriseEncryption.IsEncrypted(apiKeyText))
|
||||
LOGGER.LogWarning($"The configured embedding provider {idx} contains a plaintext API key. Only encrypted API keys (starting with 'ENC:v1:') are supported.");
|
||||
else
|
||||
{
|
||||
var encryption = PluginFactory.EnterpriseEncryption;
|
||||
if (encryption?.IsAvailable == true)
|
||||
{
|
||||
if (encryption.TryDecrypt(apiKeyText, out var decryptedApiKey))
|
||||
{
|
||||
// Queue the API key for storage in the OS keyring:
|
||||
PendingEnterpriseApiKeys.Add(new(
|
||||
$"{ISecretId.ENTERPRISE_KEY_PREFIX}::{usedLLMProvider.ToName()}",
|
||||
name,
|
||||
decryptedApiKey,
|
||||
SecretStoreType.EMBEDDING_PROVIDER));
|
||||
LOGGER.LogDebug($"Successfully decrypted API key for embedding provider {idx}. It will be stored in the OS keyring.");
|
||||
}
|
||||
else
|
||||
LOGGER.LogWarning($"Failed to decrypt API key for embedding provider {idx}. The encryption secret may be incorrect.");
|
||||
}
|
||||
else
|
||||
LOGGER.LogWarning($"The configured embedding provider {idx} contains an encrypted API key, but no encryption secret is configured.");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -131,4 +159,36 @@ public sealed record EmbeddingProvider(
|
||||
model = new(id, displayName);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exports the embedding provider configuration as a Lua configuration section.
|
||||
/// </summary>
|
||||
/// <param name="encryptedApiKey">Optional encrypted API key to include in the export.</param>
|
||||
/// <returns>A Lua configuration section string.</returns>
|
||||
public string ExportAsConfigurationSection(string? encryptedApiKey = null)
|
||||
{
|
||||
var apiKeyLine = string.Empty;
|
||||
if (!string.IsNullOrWhiteSpace(encryptedApiKey))
|
||||
{
|
||||
apiKeyLine = $"""
|
||||
["APIKey"] = "{LuaTools.EscapeLuaString(encryptedApiKey)}",
|
||||
""";
|
||||
}
|
||||
|
||||
return $$"""
|
||||
CONFIG["EMBEDDING_PROVIDERS"][#CONFIG["EMBEDDING_PROVIDERS"]+1] = {
|
||||
["Id"] = "{{Guid.NewGuid().ToString()}}",
|
||||
["Name"] = "{{LuaTools.EscapeLuaString(this.Name)}}",
|
||||
["UsedLLMProvider"] = "{{this.UsedLLMProvider}}",
|
||||
|
||||
["Host"] = "{{this.Host}}",
|
||||
["Hostname"] = "{{LuaTools.EscapeLuaString(this.Hostname)}}",
|
||||
{{apiKeyLine}}
|
||||
["Model"] = {
|
||||
["Id"] = "{{LuaTools.EscapeLuaString(this.Model.Id)}}",
|
||||
["DisplayName"] = "{{LuaTools.EscapeLuaString(this.Model.DisplayName ?? string.Empty)}}",
|
||||
},
|
||||
}
|
||||
""";
|
||||
}
|
||||
}
|
||||
@ -71,7 +71,7 @@ public sealed record Provider(
|
||||
|
||||
/// <inheritdoc />
|
||||
[JsonIgnore]
|
||||
public string SecretId => this.Id;
|
||||
public string SecretId => this.IsEnterpriseConfiguration ? $"{ISecretId.ENTERPRISE_KEY_PREFIX}::{this.UsedLLMProvider.ToName()}" : this.UsedLLMProvider.ToName();
|
||||
|
||||
/// <inheritdoc />
|
||||
[JsonIgnore]
|
||||
@ -122,6 +122,16 @@ public sealed record Provider(
|
||||
return false;
|
||||
}
|
||||
|
||||
var hfInferenceProvider = HFInferenceProvider.NONE;
|
||||
if (table.TryGetValue("HFInferenceProvider", out var hfInferenceProviderValue) && hfInferenceProviderValue.TryRead<string>(out var hfInferenceProviderText))
|
||||
{
|
||||
if (!Enum.TryParse(hfInferenceProviderText, true, out hfInferenceProvider))
|
||||
{
|
||||
LOGGER.LogWarning($"The configured provider {idx} does not contain a valid Hugging Face inference provider enum value.");
|
||||
hfInferenceProvider = HFInferenceProvider.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
if (!table.TryGetValue("Model", out var modelValue) || !modelValue.TryRead<LuaTable>(out var modelTable))
|
||||
{
|
||||
LOGGER.LogWarning($"The configured provider {idx} does not contain a valid model table.");
|
||||
@ -153,9 +163,38 @@ public sealed record Provider(
|
||||
EnterpriseConfigurationPluginId = configPluginId,
|
||||
Hostname = hostname,
|
||||
Host = host,
|
||||
HFInferenceProvider = hfInferenceProvider,
|
||||
AdditionalJsonApiParameters = additionalJsonApiParameters,
|
||||
};
|
||||
|
||||
// Handle encrypted API key if present:
|
||||
if (table.TryGetValue("APIKey", out var apiKeyValue) && apiKeyValue.TryRead<string>(out var apiKeyText) && !string.IsNullOrWhiteSpace(apiKeyText))
|
||||
{
|
||||
if (!EnterpriseEncryption.IsEncrypted(apiKeyText))
|
||||
LOGGER.LogWarning($"The configured provider {idx} contains a plaintext API key. Only encrypted API keys (starting with 'ENC:v1:') are supported.");
|
||||
else
|
||||
{
|
||||
var encryption = PluginFactory.EnterpriseEncryption;
|
||||
if (encryption?.IsAvailable == true)
|
||||
{
|
||||
if (encryption.TryDecrypt(apiKeyText, out var decryptedApiKey))
|
||||
{
|
||||
// Queue the API key for storage in the OS keyring:
|
||||
PendingEnterpriseApiKeys.Add(new(
|
||||
$"{ISecretId.ENTERPRISE_KEY_PREFIX}::{usedLLMProvider.ToName()}",
|
||||
instanceName,
|
||||
decryptedApiKey,
|
||||
SecretStoreType.LLM_PROVIDER));
|
||||
LOGGER.LogDebug($"Successfully decrypted API key for provider {idx}. It will be stored in the OS keyring.");
|
||||
}
|
||||
else
|
||||
LOGGER.LogWarning($"Failed to decrypt API key for provider {idx}. The encryption secret may be incorrect.");
|
||||
}
|
||||
else
|
||||
LOGGER.LogWarning($"The configured provider {idx} contains an encrypted API key, but no encryption secret is configured.");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -177,4 +216,46 @@ public sealed record Provider(
|
||||
model = new(id, displayName);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exports the provider configuration as a Lua configuration section.
|
||||
/// </summary>
|
||||
/// <param name="encryptedApiKey">Optional encrypted API key to include in the export.</param>
|
||||
/// <returns>A Lua configuration section string.</returns>
|
||||
public string ExportAsConfigurationSection(string? encryptedApiKey = null)
|
||||
{
|
||||
var hfInferenceProviderLine = string.Empty;
|
||||
if (this.HFInferenceProvider is not HFInferenceProvider.NONE)
|
||||
{
|
||||
hfInferenceProviderLine = $"""
|
||||
["HFInferenceProvider"] = "{this.HFInferenceProvider}",
|
||||
""";
|
||||
}
|
||||
|
||||
var apiKeyLine = string.Empty;
|
||||
if (!string.IsNullOrWhiteSpace(encryptedApiKey))
|
||||
{
|
||||
apiKeyLine = $"""
|
||||
["APIKey"] = "{LuaTools.EscapeLuaString(encryptedApiKey)}",
|
||||
""";
|
||||
}
|
||||
|
||||
return $$"""
|
||||
CONFIG["LLM_PROVIDERS"][#CONFIG["LLM_PROVIDERS"]+1] = {
|
||||
["Id"] = "{{Guid.NewGuid().ToString()}}",
|
||||
["InstanceName"] = "{{LuaTools.EscapeLuaString(this.InstanceName)}}",
|
||||
["UsedLLMProvider"] = "{{this.UsedLLMProvider}}",
|
||||
|
||||
["Host"] = "{{this.Host}}",
|
||||
["Hostname"] = "{{LuaTools.EscapeLuaString(this.Hostname)}}",
|
||||
{{hfInferenceProviderLine}}
|
||||
{{apiKeyLine}}
|
||||
["AdditionalJsonApiParameters"] = "{{LuaTools.EscapeLuaString(this.AdditionalJsonApiParameters)}}",
|
||||
["Model"] = {
|
||||
["Id"] = "{{LuaTools.EscapeLuaString(this.Model.Id)}}",
|
||||
["DisplayName"] = "{{LuaTools.EscapeLuaString(this.Model.DisplayName ?? string.Empty)}}",
|
||||
},
|
||||
}
|
||||
""";
|
||||
}
|
||||
}
|
||||
@ -43,7 +43,7 @@ public sealed record TranscriptionProvider(
|
||||
|
||||
/// <inheritdoc />
|
||||
[JsonIgnore]
|
||||
public string SecretId => this.Id;
|
||||
public string SecretId => this.IsEnterpriseConfiguration ? $"{ISecretId.ENTERPRISE_KEY_PREFIX}::{this.UsedLLMProvider.ToName()}" : this.UsedLLMProvider.ToName();
|
||||
|
||||
/// <inheritdoc />
|
||||
[JsonIgnore]
|
||||
@ -110,6 +110,34 @@ public sealed record TranscriptionProvider(
|
||||
Host = host,
|
||||
};
|
||||
|
||||
// Handle encrypted API key if present:
|
||||
if (table.TryGetValue("APIKey", out var apiKeyValue) && apiKeyValue.TryRead<string>(out var apiKeyText) && !string.IsNullOrWhiteSpace(apiKeyText))
|
||||
{
|
||||
if (!EnterpriseEncryption.IsEncrypted(apiKeyText))
|
||||
LOGGER.LogWarning($"The configured transcription provider {idx} contains a plaintext API key. Only encrypted API keys (starting with 'ENC:v1:') are supported.");
|
||||
else
|
||||
{
|
||||
var encryption = PluginFactory.EnterpriseEncryption;
|
||||
if (encryption?.IsAvailable == true)
|
||||
{
|
||||
if (encryption.TryDecrypt(apiKeyText, out var decryptedApiKey))
|
||||
{
|
||||
// Queue the API key for storage in the OS keyring:
|
||||
PendingEnterpriseApiKeys.Add(new(
|
||||
$"{ISecretId.ENTERPRISE_KEY_PREFIX}::{usedLLMProvider.ToName()}",
|
||||
name,
|
||||
decryptedApiKey,
|
||||
SecretStoreType.TRANSCRIPTION_PROVIDER));
|
||||
LOGGER.LogDebug($"Successfully decrypted API key for transcription provider {idx}. It will be stored in the OS keyring.");
|
||||
}
|
||||
else
|
||||
LOGGER.LogWarning($"Failed to decrypt API key for transcription provider {idx}. The encryption secret may be incorrect.");
|
||||
}
|
||||
else
|
||||
LOGGER.LogWarning($"The configured transcription provider {idx} contains an encrypted API key, but no encryption secret is configured.");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -131,4 +159,36 @@ public sealed record TranscriptionProvider(
|
||||
model = new(id, displayName);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exports the transcription provider configuration as a Lua configuration section.
|
||||
/// </summary>
|
||||
/// <param name="encryptedApiKey">Optional encrypted API key to include in the export.</param>
|
||||
/// <returns>A Lua configuration section string.</returns>
|
||||
public string ExportAsConfigurationSection(string? encryptedApiKey = null)
|
||||
{
|
||||
var apiKeyLine = string.Empty;
|
||||
if (!string.IsNullOrWhiteSpace(encryptedApiKey))
|
||||
{
|
||||
apiKeyLine = $"""
|
||||
["APIKey"] = "{LuaTools.EscapeLuaString(encryptedApiKey)}",
|
||||
""";
|
||||
}
|
||||
|
||||
return $$"""
|
||||
CONFIG["TRANSCRIPTION_PROVIDERS"][#CONFIG["TRANSCRIPTION_PROVIDERS"]+1] = {
|
||||
["Id"] = "{{Guid.NewGuid().ToString()}}",
|
||||
["Name"] = "{{LuaTools.EscapeLuaString(this.Name)}}",
|
||||
["UsedLLMProvider"] = "{{this.UsedLLMProvider}}",
|
||||
|
||||
["Host"] = "{{this.Host}}",
|
||||
["Hostname"] = "{{LuaTools.EscapeLuaString(this.Hostname)}}",
|
||||
{{apiKeyLine}}
|
||||
["Model"] = {
|
||||
["Id"] = "{{LuaTools.EscapeLuaString(this.Model.Id)}}",
|
||||
["DisplayName"] = "{{LuaTools.EscapeLuaString(this.Model.DisplayName ?? string.Empty)}}",
|
||||
},
|
||||
}
|
||||
""";
|
||||
}
|
||||
}
|
||||
211
app/MindWork AI Studio/Tools/EnterpriseEncryption.cs
Normal file
211
app/MindWork AI Studio/Tools/EnterpriseEncryption.cs
Normal file
@ -0,0 +1,211 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace AIStudio.Tools;
|
||||
|
||||
/// <summary>
|
||||
/// Provides encryption and decryption functionality for enterprise configuration plugins.
|
||||
/// This is used to encrypt/decrypt API keys in Lua configuration files.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Important: This is obfuscation, not security. Users with administrative access
|
||||
/// to their machines can potentially extract the decrypted API keys. This feature
|
||||
/// is designed to prevent casual exposure of API keys in configuration files. It
|
||||
/// also protects against accidental leaks while sharing configuration snippets,
|
||||
/// as the encrypted values cannot be decrypted without the secret key.
|
||||
/// </remarks>
|
||||
public sealed class EnterpriseEncryption
|
||||
{
|
||||
/// <summary>
|
||||
/// The number of iterations to derive the key and IV from the password.
|
||||
/// We use a higher iteration count here because the secret is static
|
||||
/// (not regenerated each startup like the IPC encryption).
|
||||
/// </summary>
|
||||
private const int ITERATIONS = 10_000;
|
||||
|
||||
/// <summary>
|
||||
/// The length of the salt in bytes.
|
||||
/// </summary>
|
||||
private const int SALT_LENGTH = 16;
|
||||
|
||||
/// <summary>
|
||||
/// The prefix for encrypted values.
|
||||
/// </summary>
|
||||
private const string PREFIX = "ENC:v1:";
|
||||
|
||||
private readonly ILogger<EnterpriseEncryption> logger;
|
||||
private readonly byte[]? secretKey;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the encryption service is available.
|
||||
/// </summary>
|
||||
public bool IsAvailable { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the enterprise encryption service.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger instance.</param>
|
||||
/// <param name="base64Secret">The base64-encoded 32-byte encryption secret.</param>
|
||||
public EnterpriseEncryption(ILogger<EnterpriseEncryption> logger, string? base64Secret)
|
||||
{
|
||||
this.logger = logger;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(base64Secret))
|
||||
{
|
||||
this.logger.LogWarning("No enterprise encryption secret configured. Encrypted API keys in configuration plugins will not be available.");
|
||||
this.IsAvailable = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
this.secretKey = Convert.FromBase64String(base64Secret);
|
||||
if (this.secretKey.Length != 32)
|
||||
{
|
||||
this.logger.LogWarning($"The enterprise encryption secret must be exactly 32 bytes (256 bits). Got {this.secretKey.Length} bytes.");
|
||||
this.secretKey = null;
|
||||
this.IsAvailable = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.IsAvailable = true;
|
||||
this.logger.LogInformation("Enterprise encryption service initialized successfully.");
|
||||
}
|
||||
catch (FormatException ex)
|
||||
{
|
||||
this.logger.LogWarning(ex, "Failed to decode the enterprise encryption secret from base64.");
|
||||
this.IsAvailable = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the given value is encrypted (has the encryption prefix).
|
||||
/// </summary>
|
||||
/// <param name="value">The value to check.</param>
|
||||
/// <returns>True if the value starts with the encryption prefix; otherwise, false.</returns>
|
||||
public static bool IsEncrypted(string? value) => value?.StartsWith(PREFIX, StringComparison.Ordinal) ?? false;
|
||||
|
||||
/// <summary>
|
||||
/// Tries to decrypt an encrypted value.
|
||||
/// </summary>
|
||||
/// <param name="encryptedValue">The encrypted value (with ENC:v1: prefix).</param>
|
||||
/// <param name="decryptedValue">When successful, contains the decrypted plaintext.</param>
|
||||
/// <returns>True if decryption was successful; otherwise, false.</returns>
|
||||
public bool TryDecrypt(string encryptedValue, out string decryptedValue)
|
||||
{
|
||||
decryptedValue = string.Empty;
|
||||
if (!this.IsAvailable)
|
||||
{
|
||||
this.logger.LogWarning("Cannot decrypt: Enterprise encryption service is not available.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsEncrypted(encryptedValue))
|
||||
{
|
||||
this.logger.LogWarning("Cannot decrypt: Value does not have the expected encryption prefix.");
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Extract the base64-encoded data after the prefix:
|
||||
var base64Data = encryptedValue[PREFIX.Length..];
|
||||
var encryptedBytes = Convert.FromBase64String(base64Data);
|
||||
if (encryptedBytes.Length < SALT_LENGTH + 1)
|
||||
{
|
||||
this.logger.LogWarning("Cannot decrypt: Encrypted data is too short.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract salt and encrypted content:
|
||||
var salt = encryptedBytes[..SALT_LENGTH];
|
||||
var cipherText = encryptedBytes[SALT_LENGTH..];
|
||||
|
||||
// Derive key and IV using PBKDF2:
|
||||
using var keyDerivation = new Rfc2898DeriveBytes(this.secretKey!, salt, ITERATIONS, HashAlgorithmName.SHA512);
|
||||
var key = keyDerivation.GetBytes(32); // AES-256
|
||||
var iv = keyDerivation.GetBytes(16); // AES block size
|
||||
|
||||
// Decrypt using AES-256-CBC:
|
||||
using var aes = Aes.Create();
|
||||
aes.Key = key;
|
||||
aes.IV = iv;
|
||||
aes.Mode = CipherMode.CBC;
|
||||
aes.Padding = PaddingMode.PKCS7;
|
||||
|
||||
using var decryptor = aes.CreateDecryptor();
|
||||
var decryptedBytes = decryptor.TransformFinalBlock(cipherText, 0, cipherText.Length);
|
||||
decryptedValue = Encoding.UTF8.GetString(decryptedBytes);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (FormatException ex)
|
||||
{
|
||||
this.logger.LogWarning(ex, "Failed to decode encrypted value from base64.");
|
||||
return false;
|
||||
}
|
||||
catch (CryptographicException ex)
|
||||
{
|
||||
this.logger.LogWarning(ex, "Failed to decrypt value. The encryption secret may be incorrect.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts a plaintext value.
|
||||
/// </summary>
|
||||
/// <param name="plaintext">The plaintext to encrypt.</param>
|
||||
/// <param name="encryptedValue">When successful, contains the encrypted value with prefix.</param>
|
||||
/// <returns>True if encryption was successful; otherwise, false.</returns>
|
||||
public bool TryEncrypt(string plaintext, out string encryptedValue)
|
||||
{
|
||||
encryptedValue = string.Empty;
|
||||
if (!this.IsAvailable)
|
||||
{
|
||||
this.logger.LogWarning("Cannot encrypt: Enterprise encryption service is not available.");
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Generate a random salt:
|
||||
var salt = RandomNumberGenerator.GetBytes(SALT_LENGTH);
|
||||
|
||||
// Derive key and IV using PBKDF2:
|
||||
using var keyDerivation = new Rfc2898DeriveBytes(this.secretKey!, salt, ITERATIONS, HashAlgorithmName.SHA512);
|
||||
var key = keyDerivation.GetBytes(32); // AES-256
|
||||
var iv = keyDerivation.GetBytes(16); // AES block size
|
||||
|
||||
// Encrypt using AES-256-CBC:
|
||||
using var aes = Aes.Create();
|
||||
aes.Key = key;
|
||||
aes.IV = iv;
|
||||
aes.Mode = CipherMode.CBC;
|
||||
aes.Padding = PaddingMode.PKCS7;
|
||||
|
||||
using var encryptor = aes.CreateEncryptor();
|
||||
var plaintextBytes = Encoding.UTF8.GetBytes(plaintext);
|
||||
var cipherText = encryptor.TransformFinalBlock(plaintextBytes, 0, plaintextBytes.Length);
|
||||
|
||||
// Combine salt and ciphertext
|
||||
var combined = new byte[SALT_LENGTH + cipherText.Length];
|
||||
Array.Copy(salt, 0, combined, 0, SALT_LENGTH);
|
||||
Array.Copy(cipherText, 0, combined, SALT_LENGTH, cipherText.Length);
|
||||
|
||||
// Encode to base64 and add the prefix:
|
||||
encryptedValue = PREFIX + Convert.ToBase64String(combined);
|
||||
return true;
|
||||
}
|
||||
catch (CryptographicException ex)
|
||||
{
|
||||
this.logger.LogWarning(ex, "Failed to encrypt value.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a new random 32-byte secret key and returns it as a base64 string.
|
||||
/// </summary>
|
||||
/// <returns>A base64-encoded 32-byte secret key.</returns>
|
||||
public static string GenerateSecret() => Convert.ToBase64String(RandomNumberGenerator.GetBytes(32));
|
||||
}
|
||||
@ -5,6 +5,13 @@ namespace AIStudio.Tools;
|
||||
/// </summary>
|
||||
public interface ISecretId
|
||||
{
|
||||
/// <summary>
|
||||
/// Prefix used for secrets imported from enterprise configuration plugins.
|
||||
/// This helps distinguish enterprise-managed keys from user-added keys
|
||||
/// in the OS keyring.
|
||||
/// </summary>
|
||||
public const string ENTERPRISE_KEY_PREFIX = "config-plugin";
|
||||
|
||||
/// <summary>
|
||||
/// The unique ID of the secret.
|
||||
/// </summary>
|
||||
|
||||
16
app/MindWork AI Studio/Tools/LuaTools.cs
Normal file
16
app/MindWork AI Studio/Tools/LuaTools.cs
Normal file
@ -0,0 +1,16 @@
|
||||
namespace AIStudio.Tools;
|
||||
|
||||
public static class LuaTools
|
||||
{
|
||||
public static string EscapeLuaString(string? value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return string.Empty;
|
||||
|
||||
return value
|
||||
.Replace("\\", "\\\\")
|
||||
.Replace("\"", "\\\"")
|
||||
.Replace("\r", "\\r")
|
||||
.Replace("\n", "\\n");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
namespace AIStudio.Tools.PluginSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a pending API key that needs to be stored in the OS keyring.
|
||||
/// This is used during plugin loading to collect API keys from configuration plugins
|
||||
/// before storing them asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="SecretId">The secret ID (provider ID).</param>
|
||||
/// <param name="SecretName">The secret name (provider instance name).</param>
|
||||
/// <param name="ApiKey">The decrypted API key.</param>
|
||||
/// <param name="StoreType">The type of secret store to use.</param>
|
||||
public sealed record PendingEnterpriseApiKey(
|
||||
string SecretId,
|
||||
string SecretName,
|
||||
string ApiKey,
|
||||
SecretStoreType StoreType);
|
||||
|
||||
/// <summary>
|
||||
/// Static container for pending API keys during plugin loading.
|
||||
/// </summary>
|
||||
public static class PendingEnterpriseApiKeys
|
||||
{
|
||||
private static readonly List<PendingEnterpriseApiKey> PENDING_KEYS = [];
|
||||
private static readonly Lock LOCK = new();
|
||||
|
||||
/// <summary>
|
||||
/// Adds a pending API key to the list.
|
||||
/// </summary>
|
||||
/// <param name="key">The pending API key to add.</param>
|
||||
public static void Add(PendingEnterpriseApiKey key)
|
||||
{
|
||||
lock (LOCK)
|
||||
PENDING_KEYS.Add(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets and clears all pending API keys.
|
||||
/// </summary>
|
||||
/// <returns>A list of all pending API keys.</returns>
|
||||
public static IReadOnlyList<PendingEnterpriseApiKey> GetAndClear()
|
||||
{
|
||||
lock (LOCK)
|
||||
{
|
||||
var keys = PENDING_KEYS.ToList();
|
||||
PENDING_KEYS.Clear();
|
||||
return keys;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
using Lua;
|
||||
|
||||
@ -8,6 +9,7 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(PluginConfiguration).Namespace, nameof(PluginConfiguration));
|
||||
private static readonly SettingsManager SETTINGS_MANAGER = Program.SERVICE_PROVIDER.GetRequiredService<SettingsManager>();
|
||||
private static readonly ILogger LOG = Program.LOGGER_FACTORY.CreateLogger(nameof(PluginConfiguration));
|
||||
|
||||
private List<PluginConfigurationObject> configObjects = [];
|
||||
|
||||
@ -23,11 +25,50 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
|
||||
|
||||
if (!dryRun)
|
||||
{
|
||||
// Store any decrypted API keys from enterprise configuration in the OS keyring:
|
||||
await StoreEnterpriseApiKeysAsync();
|
||||
|
||||
await SETTINGS_MANAGER.StoreSettings();
|
||||
await MessageBus.INSTANCE.SendMessage<bool>(null, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores any pending enterprise API keys in the OS keyring.
|
||||
/// </summary>
|
||||
private static async Task StoreEnterpriseApiKeysAsync()
|
||||
{
|
||||
var pendingKeys = PendingEnterpriseApiKeys.GetAndClear();
|
||||
if (pendingKeys.Count == 0)
|
||||
return;
|
||||
|
||||
LOG.LogInformation($"Storing {pendingKeys.Count} enterprise API key(s) in the OS keyring.");
|
||||
var rustService = Program.SERVICE_PROVIDER.GetRequiredService<RustService>();
|
||||
foreach (var pendingKey in pendingKeys)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Create a temporary secret ID object for storing the key:
|
||||
var secretId = new TemporarySecretId(pendingKey.SecretId, pendingKey.SecretName);
|
||||
var result = await rustService.SetAPIKey(secretId, pendingKey.ApiKey, pendingKey.StoreType);
|
||||
|
||||
if (result.Success)
|
||||
LOG.LogDebug($"Successfully stored enterprise API key for '{pendingKey.SecretName}' in the OS keyring.");
|
||||
else
|
||||
LOG.LogWarning($"Failed to store enterprise API key for '{pendingKey.SecretName}': {result.Issue}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LOG.LogError(ex, $"Exception while storing enterprise API key for '{pendingKey.SecretName}'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Temporary implementation of ISecretId for storing enterprise API keys.
|
||||
/// </summary>
|
||||
private sealed record TemporarySecretId(string SecretId, string SecretName) : ISecretId;
|
||||
|
||||
/// <summary>
|
||||
/// Tries to initialize the UI text content of the plugin.
|
||||
/// </summary>
|
||||
@ -61,6 +102,9 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
|
||||
// Config: allow the user to add providers?
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.AllowUserToAddProvider, this.Id, settingsTable, dryRun);
|
||||
|
||||
// Config: show administration settings?
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.ShowAdminSettings, this.Id, settingsTable, dryRun);
|
||||
|
||||
// Config: preview features visibility
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.PreviewVisibility, this.Id, settingsTable, dryRun);
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ using System.Linq.Expressions;
|
||||
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Settings.DataModel;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
using Lua;
|
||||
|
||||
@ -13,6 +14,7 @@ namespace AIStudio.Tools.PluginSystem;
|
||||
/// </summary>
|
||||
public sealed record PluginConfigurationObject
|
||||
{
|
||||
private static readonly RustService RUST_SERVICE = Program.SERVICE_PROVIDER.GetRequiredService<RustService>();
|
||||
private static readonly SettingsManager SETTINGS_MANAGER = Program.SERVICE_PROVIDER.GetRequiredService<SettingsManager>();
|
||||
private static readonly ILogger LOG = Program.LOGGER_FACTORY.CreateLogger<PluginConfigurationObject>();
|
||||
|
||||
@ -168,37 +170,45 @@ public sealed record PluginConfigurationObject
|
||||
/// <param name="configObjectSelection">A selection expression to retrieve the configuration objects from the main configuration.</param>
|
||||
/// <param name="availablePlugins">A list of currently available plugins.</param>
|
||||
/// <param name="configObjectList">A list of all existing configuration objects.</param>
|
||||
/// <param name="secretStoreType">An optional parameter specifying the type of secret store to use for deleting associated API keys from the OS keyring, if applicable.</param>
|
||||
/// <returns>Returns true if the configuration was altered during cleanup; otherwise, false.</returns>
|
||||
public static bool CleanLeftOverConfigurationObjects<TClass>(
|
||||
public static async Task<bool> CleanLeftOverConfigurationObjects<TClass>(
|
||||
PluginConfigurationObjectType configObjectType,
|
||||
Expression<Func<Data, List<TClass>>> configObjectSelection,
|
||||
IList<IAvailablePlugin> availablePlugins,
|
||||
IList<PluginConfigurationObject> configObjectList) where TClass : IConfigurationObject
|
||||
IList<PluginConfigurationObject> configObjectList,
|
||||
SecretStoreType? secretStoreType = null) where TClass : IConfigurationObject
|
||||
{
|
||||
var configuredObjects = configObjectSelection.Compile()(SETTINGS_MANAGER.ConfigurationData);
|
||||
var leftOverObjects = new List<TClass>();
|
||||
foreach (var configuredObject in configuredObjects)
|
||||
{
|
||||
// Only process objects that are based on enterprise configuration plugins (aka configuration plugins),
|
||||
// as only those can be left over after a plugin was removed:
|
||||
if(!configuredObject.IsEnterpriseConfiguration)
|
||||
continue;
|
||||
|
||||
// From what plugin is this configuration object coming from?
|
||||
var configObjectSourcePluginId = configuredObject.EnterpriseConfigurationPluginId;
|
||||
if(configObjectSourcePluginId == Guid.Empty)
|
||||
continue;
|
||||
|
||||
// Is the source plugin still available? If not, we can be pretty sure that this configuration object is left
|
||||
// over and should be removed:
|
||||
var templateSourcePlugin = availablePlugins.FirstOrDefault(plugin => plugin.Id == configObjectSourcePluginId);
|
||||
if(templateSourcePlugin is null)
|
||||
{
|
||||
LOG.LogWarning($"The configured object '{configuredObject.Name}' (id={configuredObject.Id}) is based on a plugin that is not available anymore. Removing the chat template from the settings.");
|
||||
LOG.LogWarning($"The configured object '{configuredObject.Name}' (id={configuredObject.Id}) is based on a plugin that is not available anymore. Removing this object from the settings.");
|
||||
leftOverObjects.Add(configuredObject);
|
||||
}
|
||||
|
||||
// Is the configuration object still present in the configuration plugin? If not, it is also left over and should be removed:
|
||||
if(!configObjectList.Any(configObject =>
|
||||
configObject.Type == configObjectType &&
|
||||
configObject.ConfigPluginId == configObjectSourcePluginId &&
|
||||
configObject.Id.ToString() == configuredObject.Id))
|
||||
{
|
||||
LOG.LogWarning($"The configured object '{configuredObject.Name}' (id={configuredObject.Id}) is not present in the configuration plugin anymore. Removing the chat template from the settings.");
|
||||
LOG.LogWarning($"The configured object '{configuredObject.Name}' (id={configuredObject.Id}) is not present in the configuration plugin anymore. Removing the object from the settings.");
|
||||
leftOverObjects.Add(configuredObject);
|
||||
}
|
||||
}
|
||||
@ -206,8 +216,20 @@ public sealed record PluginConfigurationObject
|
||||
// Remove collected items after enumeration to avoid modifying the collection during iteration:
|
||||
var wasConfigurationChanged = leftOverObjects.Count > 0;
|
||||
foreach (var item in leftOverObjects.Distinct())
|
||||
{
|
||||
configuredObjects.Remove(item);
|
||||
|
||||
// Delete the API key from the OS keyring if the removed object has one:
|
||||
if(secretStoreType is not null && item is ISecretId secretId)
|
||||
{
|
||||
var deleteResult = await RUST_SERVICE.DeleteAPIKey(secretId, secretStoreType.Value);
|
||||
if (deleteResult.Success)
|
||||
LOG.LogInformation($"Successfully deleted API key for removed enterprise provider '{item.Name}' from the OS keyring.");
|
||||
else
|
||||
LOG.LogWarning($"Failed to delete API key for removed enterprise provider '{item.Name}' from the OS keyring: {deleteResult.Issue}");
|
||||
}
|
||||
}
|
||||
|
||||
return wasConfigurationChanged;
|
||||
}
|
||||
}
|
||||
@ -131,26 +131,26 @@ public static partial class PluginFactory
|
||||
//
|
||||
|
||||
// Check LLM providers:
|
||||
var wasConfigurationChanged = PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.LLM_PROVIDER, x => x.Providers, AVAILABLE_PLUGINS, configObjectList);
|
||||
var wasConfigurationChanged = await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.LLM_PROVIDER, x => x.Providers, AVAILABLE_PLUGINS, configObjectList, SecretStoreType.LLM_PROVIDER);
|
||||
|
||||
// Check transcription providers:
|
||||
if(PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.TRANSCRIPTION_PROVIDER, x => x.TranscriptionProviders, AVAILABLE_PLUGINS, configObjectList))
|
||||
if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.TRANSCRIPTION_PROVIDER, x => x.TranscriptionProviders, AVAILABLE_PLUGINS, configObjectList, SecretStoreType.TRANSCRIPTION_PROVIDER))
|
||||
wasConfigurationChanged = true;
|
||||
|
||||
// Check embedding providers:
|
||||
if(PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.EMBEDDING_PROVIDER, x => x.EmbeddingProviders, AVAILABLE_PLUGINS, configObjectList))
|
||||
if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.EMBEDDING_PROVIDER, x => x.EmbeddingProviders, AVAILABLE_PLUGINS, configObjectList, SecretStoreType.EMBEDDING_PROVIDER))
|
||||
wasConfigurationChanged = true;
|
||||
|
||||
// Check chat templates:
|
||||
if(PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.CHAT_TEMPLATE, x => x.ChatTemplates, AVAILABLE_PLUGINS, configObjectList))
|
||||
if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.CHAT_TEMPLATE, x => x.ChatTemplates, AVAILABLE_PLUGINS, configObjectList))
|
||||
wasConfigurationChanged = true;
|
||||
|
||||
// Check profiles:
|
||||
if(PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.PROFILE, x => x.Profiles, AVAILABLE_PLUGINS, configObjectList))
|
||||
if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.PROFILE, x => x.Profiles, AVAILABLE_PLUGINS, configObjectList))
|
||||
wasConfigurationChanged = true;
|
||||
|
||||
// Check document analysis policies:
|
||||
if(PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.DOCUMENT_ANALYSIS_POLICY, x => x.DocumentAnalysis.Policies, AVAILABLE_PLUGINS, configObjectList))
|
||||
if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.DOCUMENT_ANALYSIS_POLICY, x => x.DocumentAnalysis.Policies, AVAILABLE_PLUGINS, configObjectList))
|
||||
wasConfigurationChanged = true;
|
||||
|
||||
// Check for a preselected profile:
|
||||
@ -169,6 +169,10 @@ public static partial class PluginFactory
|
||||
if(ManagedConfiguration.IsConfigurationLeftOver(x => x.App, x => x.AllowUserToAddProvider, AVAILABLE_PLUGINS))
|
||||
wasConfigurationChanged = true;
|
||||
|
||||
// Check for admin settings visibility:
|
||||
if(ManagedConfiguration.IsConfigurationLeftOver(x => x.App, x => x.ShowAdminSettings, AVAILABLE_PLUGINS))
|
||||
wasConfigurationChanged = true;
|
||||
|
||||
// Check for preview visibility:
|
||||
if(ManagedConfiguration.IsConfigurationLeftOver(x => x.App, x => x.PreviewVisibility, AVAILABLE_PLUGINS))
|
||||
wasConfigurationChanged = true;
|
||||
|
||||
@ -18,6 +18,29 @@ public static partial class PluginFactory
|
||||
|
||||
public static ILanguagePlugin BaseLanguage => BASE_LANGUAGE_PLUGIN;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the enterprise encryption instance for decrypting API keys in configuration plugins.
|
||||
/// </summary>
|
||||
public static EnterpriseEncryption? EnterpriseEncryption { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the enterprise encryption service by reading the encryption secret
|
||||
/// from the Windows Registry or environment variables.
|
||||
/// </summary>
|
||||
/// <param name="rustService">The Rust service to use for reading the encryption secret.</param>
|
||||
public static async Task InitializeEnterpriseEncryption(Services.RustService rustService)
|
||||
{
|
||||
LOG.LogInformation("Initializing enterprise encryption service...");
|
||||
var encryptionSecret = await rustService.EnterpriseEnvConfigEncryptionSecret();
|
||||
var enterpriseEncryptionLogger = Program.LOGGER_FACTORY.CreateLogger<EnterpriseEncryption>();
|
||||
EnterpriseEncryption = new EnterpriseEncryption(enterpriseEncryptionLogger, encryptionSecret);
|
||||
|
||||
if (EnterpriseEncryption.IsAvailable)
|
||||
LOG.LogInformation("Enterprise encryption service is available.");
|
||||
else
|
||||
LOG.LogWarning("Enterprise encryption service is not available (no secret configured).");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set up the plugin factory. We will read the data directory from the settings manager.
|
||||
/// Afterward, we will create the plugins directory and the internal plugin directory.
|
||||
|
||||
@ -65,4 +65,24 @@ public sealed partial class RustService
|
||||
var serverUrl = await result.Content.ReadAsStringAsync();
|
||||
return string.IsNullOrWhiteSpace(serverUrl) ? string.Empty : serverUrl;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to read the enterprise environment for the configuration encryption secret.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Returns an empty string when the environment is not set or the request fails.
|
||||
/// Otherwise, the base64-encoded encryption secret.
|
||||
/// </returns>
|
||||
public async Task<string> EnterpriseEnvConfigEncryptionSecret()
|
||||
{
|
||||
var result = await this.http.GetAsync("/system/enterprise/config/encryption_secret");
|
||||
if (!result.IsSuccessStatusCode)
|
||||
{
|
||||
this.logger!.LogError($"Failed to query the enterprise configuration encryption secret: '{result.StatusCode}'");
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var encryptionSecret = await result.Content.ReadAsStringAsync();
|
||||
return string.IsNullOrWhiteSpace(encryptionSecret) ? string.Empty : encryptionSecret;
|
||||
}
|
||||
}
|
||||
@ -62,6 +62,16 @@
|
||||
"MudBlazor": "8.11.0"
|
||||
}
|
||||
},
|
||||
"Qdrant.Client": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.16.1, )",
|
||||
"resolved": "1.16.1",
|
||||
"contentHash": "EJo50JXTdjY2JOUphCFLXoHukI/tz/ykLCmMnQHUjsKT22ZfL0XIdEziHOC3vjw2SOoY8WDVQ+AxixEonejOZA==",
|
||||
"dependencies": {
|
||||
"Google.Protobuf": "3.31.0",
|
||||
"Grpc.Net.Client": "2.71.0"
|
||||
}
|
||||
},
|
||||
"ReverseMarkdown": {
|
||||
"type": "Direct",
|
||||
"requested": "[5.0.0, )",
|
||||
@ -76,6 +86,33 @@
|
||||
"resolved": "3.2.449",
|
||||
"contentHash": "uA9sYDy4VepL3xwzBTLcP2LyuVYMt0ZIT3gaSiXvGoX15Ob+rOP+hGydhevlSVd+rFo+Y+VQFEHDuWU8HBW+XA=="
|
||||
},
|
||||
"Google.Protobuf": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.31.0",
|
||||
"contentHash": "OZXSf6igaJBeo+kAzMhYF0R5zp0nRgf4G0Uis/IsGKACc4RGP9bQPLpHLengIFuASl0lY92utMB8rRpTx4TaOg=="
|
||||
},
|
||||
"Grpc.Core.Api": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.71.0",
|
||||
"contentHash": "QquqUC37yxsDzd1QaDRsH2+uuznWPTS8CVE2Yzwl3CvU4geTNkolQXoVN812M2IwT6zpv3jsZRc9ExJFNFslTg=="
|
||||
},
|
||||
"Grpc.Net.Client": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.71.0",
|
||||
"contentHash": "U1vr20r5ngoT9nlb7wejF28EKN+taMhJsV9XtK9MkiepTZwnKxxiarriiMfCHuDAfPUm9XUjFMn/RIuJ4YY61w==",
|
||||
"dependencies": {
|
||||
"Grpc.Net.Common": "2.71.0",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "6.0.0"
|
||||
}
|
||||
},
|
||||
"Grpc.Net.Common": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.71.0",
|
||||
"contentHash": "v0c8R97TwRYwNXlC8GyRXwYTCNufpDfUtj9la+wUrZFzVWkFJuNAltU+c0yI3zu0jl54k7en6u2WKgZgd57r2Q==",
|
||||
"dependencies": {
|
||||
"Grpc.Core.Api": "2.71.0"
|
||||
}
|
||||
},
|
||||
"Markdig": {
|
||||
"type": "Transitive",
|
||||
"resolved": "0.41.3",
|
||||
|
||||
@ -1,2 +1,6 @@
|
||||
# v26.2.2, build 234 (2026-02-xx xx:xx UTC)
|
||||
- Added a vector database (Qdrant) as a building block for our local RAG (retrieval-augmented generation) solution. Thank you very much, Paul (`PaulKoudelka`), for this major contribution. Note that our local RAG implementation remained in preview and has not yet been released; other building blocks are not yet ready.
|
||||
- Added an app setting to enable administration options for IT staff to configure and maintain organization-wide settings.
|
||||
- Added an option to export all provider types (LLMs, embeddings, transcriptions) so you can use them in a configuration plugin. You'll be asked if you want to export the related API key too. API keys will be encrypted in the export. This feature only shows up when administration options are enabled.
|
||||
- Added an option in the app settings to create an encryption secret, which is required to encrypt values (for example, API keys) in configuration plugins. This feature only shows up when administration options are enabled.
|
||||
- Improved the document analysis assistant (in beta) by hiding the export functionality by default. Enable the administration options in the app settings to show and use the export functionality. This streamlines the usage for regular users.
|
||||
@ -27,6 +27,8 @@ The following keys and values (registry) and variables are checked and read:
|
||||
|
||||
- Key `HKEY_CURRENT_USER\Software\github\MindWork AI Studio\Enterprise IT`, value `config_server_url` or variable `MINDWORK_AI_STUDIO_ENTERPRISE_CONFIG_SERVER_URL`: An HTTP or HTTPS address using an IP address or DNS name. This is the web server from which AI Studio attempts to load the specified configuration as a ZIP file.
|
||||
|
||||
- Key `HKEY_CURRENT_USER\Software\github\MindWork AI Studio\Enterprise IT`, value `config_encryption_secret` or variable `MINDWORK_AI_STUDIO_ENTERPRISE_CONFIG_ENCRYPTION_SECRET`: A base64-encoded 32-byte encryption key for decrypting API keys in configuration plugins. This is optional and only needed if you want to include encrypted API keys in your configuration.
|
||||
|
||||
Let's assume as example that `https://intranet.my-company.com:30100/ai-studio/configuration` is the server address and `9072b77d-ca81-40da-be6a-861da525ef7b` is the configuration ID. AI Studio will derive the following address from this information: `https://intranet.my-company.com:30100/ai-studio/configuration/9072b77d-ca81-40da-be6a-861da525ef7b.zip`. Important: The configuration ID will always be written in lowercase, even if it is configured in uppercase. If `9072B77D-CA81-40DA-BE6A-861DA525EF7B` is configured, the same address will be derived. Your web server must be configured accordingly.
|
||||
|
||||
Finally, AI Studio will send a GET request and download the ZIP file. The ZIP file only contains the files necessary for the configuration. It's normal to include a file for an icon along with the actual configuration plugin.
|
||||
@ -82,14 +84,61 @@ The latest example of an AI Studio configuration via configuration plugin can al
|
||||
Please note that the icon must be an SVG vector graphic. Raster graphics like PNGs, GIFs, and others aren’t supported. You can use the sample icon, which looks like a gear.
|
||||
|
||||
Currently, you can configure the following things:
|
||||
- Any number of self-hosted LLM providers (a combination of server and model), but currently only without API keys
|
||||
- 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
|
||||
- The update behavior of AI Studio
|
||||
- Various UI and feature settings (see the example configuration for details)
|
||||
|
||||
All other settings can be made by the user themselves. If you need additional settings, feel free to create an issue in our planning repository: https://github.com/MindWorkAI/Planning/issues
|
||||
|
||||
In the coming months, we will allow more settings, such as:
|
||||
- Using API keys for providers
|
||||
- Configuration of embedding providers for RAG
|
||||
- Configuration of data sources for RAG
|
||||
- Configuration of chat templates
|
||||
- Configuration of assistant plugins (for example, your own assistants for your company or specific departments)
|
||||
## Encrypted API Keys
|
||||
|
||||
You can include encrypted API keys in your configuration plugins for cloud providers (like OpenAI, Anthropic) or secured on-premise models. This feature provides obfuscation to prevent casual exposure of API keys in configuration files.
|
||||
|
||||
**Important Security Note:** This is obfuscation, not absolute security. Users with administrative access to their machines can potentially extract the decrypted API keys with sufficient effort. This feature is designed to:
|
||||
- Prevent API keys from being visible in plaintext in configuration files
|
||||
- Protect against accidental exposure when sharing or reviewing configurations
|
||||
- Add a barrier against casual snooping
|
||||
|
||||
### Setting Up Encrypted API Keys
|
||||
|
||||
1. **Generate an encryption secret:**
|
||||
In AI Studio, enable the "Show administration settings" toggle in the app settings. Then click the "Generate encryption secret and copy to clipboard" button in the "Enterprise Administration" section. This generates a cryptographically secure 256-bit key and copies it to your clipboard as a base64 string.
|
||||
|
||||
2. **Deploy the encryption secret:**
|
||||
Distribute the secret to all client machines via Group Policy (Windows Registry) or environment variables:
|
||||
- Registry: `HKEY_CURRENT_USER\Software\github\MindWork AI Studio\Enterprise IT\config_encryption_secret`
|
||||
- Environment: `MINDWORK_AI_STUDIO_ENTERPRISE_CONFIG_ENCRYPTION_SECRET`
|
||||
|
||||
You must also deploy the same secret on the machine where you will export the encrypted API keys (step 3).
|
||||
|
||||
3. **Export encrypted API keys from AI Studio:**
|
||||
Once the encryption secret is deployed on your machine:
|
||||
- Configure a provider with an API key in AI Studio's settings
|
||||
- Click the export button for that provider
|
||||
- If an API key is configured, you will be asked if you want to include the encrypted API key in the export
|
||||
- The exported Lua code will contain the encrypted API key in the format `ENC:v1:<base64-encoded data>`
|
||||
|
||||
4. **Add encrypted keys to your configuration:**
|
||||
Copy the exported configuration (including the encrypted API key) into your configuration plugin.
|
||||
|
||||
### Example Configuration with Encrypted API Key
|
||||
|
||||
```lua
|
||||
CONFIG["LLM_PROVIDERS"][#CONFIG["LLM_PROVIDERS"]+1] = {
|
||||
["Id"] = "9072b77d-ca81-40da-be6a-861da525ef7b",
|
||||
["InstanceName"] = "Corporate OpenAI GPT-4",
|
||||
["UsedLLMProvider"] = "OPEN_AI",
|
||||
["Host"] = "NONE",
|
||||
["Hostname"] = "",
|
||||
["APIKey"] = "ENC:v1:MTIzNDU2Nzg5MDEyMzQ1NkFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFla...",
|
||||
["AdditionalJsonApiParameters"] = "",
|
||||
["Model"] = {
|
||||
["Id"] = "gpt-4",
|
||||
["DisplayName"] = "GPT-4",
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The API key will be automatically decrypted when the configuration is loaded and stored securely in the operating system's credential store (Windows Credential Manager / macOS Keychain).
|
||||
191
runtime/Cargo.lock
generated
191
runtime/Cargo.lock
generated
@ -1046,6 +1046,15 @@ dependencies = [
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-next"
|
||||
version = "2.0.0"
|
||||
@ -1056,6 +1065,18 @@ dependencies = [
|
||||
"dirs-sys-next",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users 0.5.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys-next"
|
||||
version = "0.1.2"
|
||||
@ -1063,7 +1084,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"redox_users",
|
||||
"redox_users 0.4.5",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
@ -1073,6 +1094,16 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
|
||||
|
||||
[[package]]
|
||||
name = "dispatch2"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
@ -2082,7 +2113,7 @@ dependencies = [
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
"windows-core 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2781,6 +2812,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"cipher",
|
||||
"crossbeam-channel",
|
||||
"dirs",
|
||||
"file-format",
|
||||
"flexi_logger",
|
||||
"futures",
|
||||
@ -2803,9 +2835,11 @@ dependencies = [
|
||||
"sha2",
|
||||
"strum_macros",
|
||||
"sys-locale",
|
||||
"sysinfo",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-window-state",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tracing-subscriber",
|
||||
@ -2942,6 +2976,15 @@ dependencies = [
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ntapi"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c70f219e21142367c70c0b30c6a9e3a14d55b4d12a204d897fbec83a0363f081"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.50.1"
|
||||
@ -3084,9 +3127,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objc2"
|
||||
version = "0.6.0"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3531f65190d9cff863b77a99857e74c314dd16bf56c538c4b57c7cbc3f3a6e59"
|
||||
checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05"
|
||||
dependencies = [
|
||||
"objc2-encode",
|
||||
]
|
||||
@ -3105,11 +3148,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-foundation"
|
||||
version = "0.3.0"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "daeaf60f25471d26948a1c2f840e3f7d86f4109e3af4e8e4b5cd70c39690d925"
|
||||
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"dispatch2",
|
||||
"objc2",
|
||||
]
|
||||
|
||||
@ -3142,6 +3186,16 @@ dependencies = [
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-io-kit"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-io-surface"
|
||||
version = "0.3.0"
|
||||
@ -3256,6 +3310,12 @@ dependencies = [
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "os_pipe"
|
||||
version = "1.2.0"
|
||||
@ -3956,6 +4016,17 @@ dependencies = [
|
||||
"thiserror 1.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
|
||||
dependencies = [
|
||||
"getrandom 0.2.15",
|
||||
"libredox",
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ref-cast"
|
||||
version = "1.0.23"
|
||||
@ -4897,6 +4968,20 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sysinfo"
|
||||
version = "0.38.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe840c5b1afe259a5657392a4dbb74473a14c8db999c3ec2f4ae812e028a94da"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"memchr",
|
||||
"ntapi",
|
||||
"objc2-core-foundation",
|
||||
"objc2-io-kit",
|
||||
"windows 0.62.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration"
|
||||
version = "0.5.1"
|
||||
@ -5008,7 +5093,7 @@ dependencies = [
|
||||
"unicode-segmentation",
|
||||
"uuid",
|
||||
"windows 0.39.0",
|
||||
"windows-implement",
|
||||
"windows-implement 0.39.0",
|
||||
"x11-dl",
|
||||
]
|
||||
|
||||
@ -6028,7 +6113,7 @@ dependencies = [
|
||||
"webview2-com-macros",
|
||||
"webview2-com-sys",
|
||||
"windows 0.39.0",
|
||||
"windows-implement",
|
||||
"windows-implement 0.39.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -6125,7 +6210,7 @@ version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1c4bd0a50ac6020f65184721f758dba47bb9fbc2133df715ec74a237b26794a"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-implement 0.39.0",
|
||||
"windows_aarch64_msvc 0.39.0",
|
||||
"windows_i686_gnu 0.39.0",
|
||||
"windows_i686_msvc 0.39.0",
|
||||
@ -6142,6 +6227,18 @@ dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.62.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
|
||||
dependencies = [
|
||||
"windows-collections",
|
||||
"windows-core 0.62.2",
|
||||
"windows-future",
|
||||
"windows-numerics",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-bindgen"
|
||||
version = "0.39.0"
|
||||
@ -6152,6 +6249,15 @@ dependencies = [
|
||||
"windows-tokens",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-collections"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
|
||||
dependencies = [
|
||||
"windows-core 0.62.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
@ -6161,6 +6267,30 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.62.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
||||
dependencies = [
|
||||
"windows-implement 0.60.2",
|
||||
"windows-interface",
|
||||
"windows-link 0.2.1",
|
||||
"windows-result 0.4.1",
|
||||
"windows-strings 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-future"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
|
||||
dependencies = [
|
||||
"windows-core 0.62.2",
|
||||
"windows-link 0.2.1",
|
||||
"windows-threading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.39.0"
|
||||
@ -6171,6 +6301,28 @@ dependencies = [
|
||||
"windows-tokens",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.93",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.93",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.1.3"
|
||||
@ -6189,6 +6341,16 @@ version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ee5e275231f07c6e240d14f34e1b635bf1faa1c76c57cfd59a5cdb9848e4278"
|
||||
|
||||
[[package]]
|
||||
name = "windows-numerics"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
|
||||
dependencies = [
|
||||
"windows-core 0.62.2",
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
version = "0.5.3"
|
||||
@ -6379,6 +6541,15 @@ dependencies = [
|
||||
"windows_x86_64_msvc 0.53.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-threading"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
|
||||
dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-tokens"
|
||||
version = "0.39.0"
|
||||
@ -6728,7 +6899,7 @@ dependencies = [
|
||||
"webkit2gtk-sys",
|
||||
"webview2-com",
|
||||
"windows 0.39.0",
|
||||
"windows-implement",
|
||||
"windows-implement 0.39.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@ -119,6 +119,30 @@ pub fn read_enterprise_env_config_server_url(_token: APIToken) -> String {
|
||||
)
|
||||
}
|
||||
|
||||
#[get("/system/enterprise/config/encryption_secret")]
|
||||
pub fn read_enterprise_env_config_encryption_secret(_token: APIToken) -> String {
|
||||
//
|
||||
// When we are on a Windows machine, we try to read the enterprise config from
|
||||
// the Windows registry. In case we can't find the registry key, or we are on a
|
||||
// macOS or Linux machine, we try to read the enterprise config from the
|
||||
// environment variables.
|
||||
//
|
||||
// The registry key is:
|
||||
// HKEY_CURRENT_USER\Software\github\MindWork AI Studio\Enterprise IT
|
||||
//
|
||||
// In this registry key, we expect the following values:
|
||||
// - config_encryption_secret
|
||||
//
|
||||
// The environment variable is:
|
||||
// MINDWORK_AI_STUDIO_ENTERPRISE_CONFIG_ENCRYPTION_SECRET
|
||||
//
|
||||
debug!("Trying to read the enterprise environment for the config encryption secret.");
|
||||
get_enterprise_configuration(
|
||||
"config_encryption_secret",
|
||||
"MINDWORK_AI_STUDIO_ENTERPRISE_CONFIG_ENCRYPTION_SECRET",
|
||||
)
|
||||
}
|
||||
|
||||
fn get_enterprise_configuration(_reg_value: &str, env_name: &str) -> String {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_os = "windows")] {
|
||||
|
||||
@ -85,6 +85,7 @@ pub fn start_runtime_api() {
|
||||
crate::environment::read_enterprise_env_config_id,
|
||||
crate::environment::delete_enterprise_env_config_id,
|
||||
crate::environment::read_enterprise_env_config_server_url,
|
||||
crate::environment::read_enterprise_env_config_encryption_secret,
|
||||
crate::file_data::extract_data,
|
||||
crate::log::get_log_paths,
|
||||
crate::log::log_event,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user