diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor b/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor
index 42dd29f9..4ffc743f 100644
--- a/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor
+++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor
@@ -36,18 +36,27 @@
@context.Name
@context.UsedLLMProvider.ToName()
@GetEmbeddingProviderModelName(context)
-
+
-
-
-
-
-
-
-
-
-
-
+
+ @if (context.IsEnterpriseConfiguration)
+ {
+
+
+
+ }
+ else
+ {
+
+
+
+
+
+
+
+
+
+ }
diff --git a/app/MindWork AI Studio/Dialogs/DataSourceLocalDirectoryDialog.razor.cs b/app/MindWork AI Studio/Dialogs/DataSourceLocalDirectoryDialog.razor.cs
index 019312da..0137f068 100644
--- a/app/MindWork AI Studio/Dialogs/DataSourceLocalDirectoryDialog.razor.cs
+++ b/app/MindWork AI Studio/Dialogs/DataSourceLocalDirectoryDialog.razor.cs
@@ -96,7 +96,7 @@ public partial class DataSourceLocalDirectoryDialog : MSGComponentBase
#endregion
- private bool SelectedCloudEmbedding => !this.SettingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id == this.dataEmbeddingId).IsSelfHosted;
+ private bool SelectedCloudEmbedding => !this.SettingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id == this.dataEmbeddingId)?.IsSelfHosted ?? false;
private DataSourceLocalDirectory CreateDataSource() => new()
{
diff --git a/app/MindWork AI Studio/Dialogs/DataSourceLocalDirectoryInfoDialog.razor.cs b/app/MindWork AI Studio/Dialogs/DataSourceLocalDirectoryInfoDialog.razor.cs
index 73bc8dc6..b56bf06a 100644
--- a/app/MindWork AI Studio/Dialogs/DataSourceLocalDirectoryInfoDialog.razor.cs
+++ b/app/MindWork AI Studio/Dialogs/DataSourceLocalDirectoryInfoDialog.razor.cs
@@ -27,7 +27,7 @@ public partial class DataSourceLocalDirectoryInfoDialog : MSGComponentBase, IAsy
protected override async Task OnInitializedAsync()
{
- this.embeddingProvider = this.SettingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id == this.DataSource.EmbeddingId);
+ this.embeddingProvider = this.SettingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id == this.DataSource.EmbeddingId) ?? EmbeddingProvider.NONE;
this.directoryInfo = new DirectoryInfo(this.DataSource.Path);
if (this.directoryInfo.Exists)
@@ -46,7 +46,7 @@ public partial class DataSourceLocalDirectoryInfoDialog : MSGComponentBase, IAsy
private readonly CancellationTokenSource cts = new();
- private EmbeddingProvider embeddingProvider;
+ private EmbeddingProvider embeddingProvider = EmbeddingProvider.NONE;
private DirectoryInfo directoryInfo = null!;
private long directorySizeBytes;
private long directorySizeNumFiles;
diff --git a/app/MindWork AI Studio/Dialogs/DataSourceLocalFileDialog.razor.cs b/app/MindWork AI Studio/Dialogs/DataSourceLocalFileDialog.razor.cs
index 76d93c35..324b0d71 100644
--- a/app/MindWork AI Studio/Dialogs/DataSourceLocalFileDialog.razor.cs
+++ b/app/MindWork AI Studio/Dialogs/DataSourceLocalFileDialog.razor.cs
@@ -96,7 +96,7 @@ public partial class DataSourceLocalFileDialog : MSGComponentBase
#endregion
- private bool SelectedCloudEmbedding => !this.SettingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id == this.dataEmbeddingId).IsSelfHosted;
+ private bool SelectedCloudEmbedding => !this.SettingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id == this.dataEmbeddingId)?.IsSelfHosted ?? false;
private DataSourceLocalFile CreateDataSource() => new()
{
diff --git a/app/MindWork AI Studio/Dialogs/DataSourceLocalFileInfoDialog.razor.cs b/app/MindWork AI Studio/Dialogs/DataSourceLocalFileInfoDialog.razor.cs
index 0601c182..68f31aff 100644
--- a/app/MindWork AI Studio/Dialogs/DataSourceLocalFileInfoDialog.razor.cs
+++ b/app/MindWork AI Studio/Dialogs/DataSourceLocalFileInfoDialog.razor.cs
@@ -18,14 +18,14 @@ public partial class DataSourceLocalFileInfoDialog : MSGComponentBase
protected override async Task OnInitializedAsync()
{
- this.embeddingProvider = this.SettingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id == this.DataSource.EmbeddingId);
+ this.embeddingProvider = this.SettingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id == this.DataSource.EmbeddingId) ?? EmbeddingProvider.NONE;
this.fileInfo = new FileInfo(this.DataSource.FilePath);
await base.OnInitializedAsync();
}
#endregion
- private EmbeddingProvider embeddingProvider;
+ private EmbeddingProvider embeddingProvider = EmbeddingProvider.NONE;
private FileInfo fileInfo = null!;
private bool IsCloudEmbedding => !this.embeddingProvider.IsSelfHosted;
diff --git a/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor.cs b/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor.cs
index d586a213..a6677686 100644
--- a/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor.cs
+++ b/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor.cs
@@ -129,6 +129,8 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
IsSelfHosted = this.DataLLMProvider is LLMProviders.SELF_HOSTED,
Hostname = cleanedHostname.EndsWith('/') ? cleanedHostname[..^1] : cleanedHostname,
Host = this.DataHost,
+ IsEnterpriseConfiguration = false,
+ EnterpriseConfigurationPluginId = Guid.Empty,
};
}
diff --git a/app/MindWork AI Studio/Plugins/configuration/plugin.lua b/app/MindWork AI Studio/Plugins/configuration/plugin.lua
index abecda7c..7e23858b 100644
--- a/app/MindWork AI Studio/Plugins/configuration/plugin.lua
+++ b/app/MindWork AI Studio/Plugins/configuration/plugin.lua
@@ -70,6 +70,24 @@ CONFIG["LLM_PROVIDERS"][#CONFIG["LLM_PROVIDERS"]+1] = {
}
}
+-- Embedding providers for local RAG (Retrieval-Augmented Generation) functionality:
+CONFIG["EMBEDDING_PROVIDERS"] = {}
+
+-- An example of an embedding provider configuration:
+-- CONFIG["EMBEDDING_PROVIDERS"][#CONFIG["EMBEDDING_PROVIDERS"]+1] = {
+-- ["Id"] = "00000000-0000-0000-0000-000000000000",
+-- ["Name"] = "",
+-- ["UsedLLMProvider"] = "SELF_HOSTED",
+--
+-- -- Allowed values for Host are: LM_STUDIO, LLAMACPP, OLLAMA, and VLLM
+-- ["Host"] = "OLLAMA",
+-- ["Hostname"] = "",
+-- ["Model"] = {
+-- ["Id"] = "",
+-- ["DisplayName"] = "",
+-- }
+-- }
+
CONFIG["SETTINGS"] = {}
-- Configure the update check interval:
diff --git a/app/MindWork AI Studio/Settings/EmbeddingProvider.cs b/app/MindWork AI Studio/Settings/EmbeddingProvider.cs
index 126a0be2..c5e48d4f 100644
--- a/app/MindWork AI Studio/Settings/EmbeddingProvider.cs
+++ b/app/MindWork AI Studio/Settings/EmbeddingProvider.cs
@@ -1,32 +1,134 @@
using System.Text.Json.Serialization;
using AIStudio.Provider;
+using AIStudio.Tools.PluginSystem;
+
+using Lua;
using Host = AIStudio.Provider.SelfHosted.Host;
namespace AIStudio.Settings;
-public readonly record struct EmbeddingProvider(
+public sealed record EmbeddingProvider(
uint Num,
string Id,
string Name,
LLMProviders UsedLLMProvider,
Model Model,
bool IsSelfHosted = false,
+ bool IsEnterpriseConfiguration = false,
+ Guid EnterpriseConfigurationPluginId = default,
string Hostname = "http://localhost:1234",
- Host Host = Host.NONE) : ISecretId
+ Host Host = Host.NONE) : ConfigurationBaseObject, ISecretId
{
+ private static readonly ILogger LOGGER = Program.LOGGER_FACTORY.CreateLogger();
+
+ public static readonly EmbeddingProvider NONE = new();
+
+ public EmbeddingProvider() : this(
+ 0,
+ Guid.Empty.ToString(),
+ string.Empty,
+ LLMProviders.NONE,
+ default,
+ false,
+ false,
+ Guid.Empty)
+ {
+ }
+
public override string ToString() => this.Name;
-
+
#region Implementation of ISecretId
-
+
///
[JsonIgnore]
public string SecretId => this.Id;
-
+
///
[JsonIgnore]
public string SecretName => this.Name;
-
+
#endregion
+
+ public static bool TryParseEmbeddingProviderTable(int idx, LuaTable table, Guid configPluginId, out ConfigurationBaseObject provider)
+ {
+ provider = NONE;
+ if (!table.TryGetValue("Id", out var idValue) || !idValue.TryRead(out var idText) || !Guid.TryParse(idText, out var id))
+ {
+ LOGGER.LogWarning($"The configured embedding provider {idx} does not contain a valid ID. The ID must be a valid GUID.");
+ return false;
+ }
+
+ if (!table.TryGetValue("Name", out var nameValue) || !nameValue.TryRead(out var name))
+ {
+ LOGGER.LogWarning($"The configured embedding provider {idx} does not contain a valid name.");
+ return false;
+ }
+
+ if (!table.TryGetValue("UsedLLMProvider", out var usedLLMProviderValue) || !usedLLMProviderValue.TryRead(out var usedLLMProviderText) || !Enum.TryParse(usedLLMProviderText, true, out var usedLLMProvider))
+ {
+ LOGGER.LogWarning($"The configured embedding provider {idx} does not contain a valid LLM provider enum value.");
+ return false;
+ }
+
+ if (!table.TryGetValue("Host", out var hostValue) || !hostValue.TryRead(out var hostText) || !Enum.TryParse(hostText, true, out var host))
+ {
+ LOGGER.LogWarning($"The configured embedding provider {idx} does not contain a valid host enum value.");
+ return false;
+ }
+
+ if (!table.TryGetValue("Hostname", out var hostnameValue) || !hostnameValue.TryRead(out var hostname))
+ {
+ LOGGER.LogWarning($"The configured embedding provider {idx} does not contain a valid hostname.");
+ return false;
+ }
+
+ if (!table.TryGetValue("Model", out var modelValue) || !modelValue.TryRead(out var modelTable))
+ {
+ LOGGER.LogWarning($"The configured embedding provider {idx} does not contain a valid model table.");
+ return false;
+ }
+
+ if (!TryReadModelTable(idx, modelTable, out var model))
+ {
+ LOGGER.LogWarning($"The configured embedding provider {idx} does not contain a valid model configuration.");
+ return false;
+ }
+
+ provider = new EmbeddingProvider
+ {
+ Num = 0,
+ Id = id.ToString(),
+ Name = name,
+ UsedLLMProvider = usedLLMProvider,
+ Model = model,
+ IsSelfHosted = usedLLMProvider is LLMProviders.SELF_HOSTED,
+ IsEnterpriseConfiguration = true,
+ EnterpriseConfigurationPluginId = configPluginId,
+ Hostname = hostname,
+ Host = host,
+ };
+
+ return true;
+ }
+
+ private static bool TryReadModelTable(int idx, LuaTable table, out Model model)
+ {
+ model = default;
+ if (!table.TryGetValue("Id", out var idValue) || !idValue.TryRead(out var id))
+ {
+ LOGGER.LogWarning($"The configured embedding provider {idx} does not contain a valid model ID.");
+ return false;
+ }
+
+ if (!table.TryGetValue("DisplayName", out var displayNameValue) || !displayNameValue.TryRead(out var displayName))
+ {
+ LOGGER.LogWarning($"The configured embedding provider {idx} does not contain a valid model display name.");
+ return false;
+ }
+
+ model = new(id, displayName);
+ return true;
+ }
}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs
index afa97c14..11aac759 100644
--- a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs
+++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs
@@ -69,7 +69,10 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
// Handle configured LLM providers:
PluginConfigurationObject.TryParse(PluginConfigurationObjectType.LLM_PROVIDER, x => x.Providers, x => x.NextProviderNum, mainTable, this.Id, ref this.configObjects, dryRun);
-
+
+ // Handle configured embedding providers:
+ PluginConfigurationObject.TryParse(PluginConfigurationObjectType.EMBEDDING_PROVIDER, x => x.EmbeddingProviders, x => x.NextEmbeddingNum, mainTable, this.Id, ref this.configObjects, dryRun);
+
// Handle configured chat templates:
PluginConfigurationObject.TryParse(PluginConfigurationObjectType.CHAT_TEMPLATE, x => x.ChatTemplates, x => x.NextChatTemplateNum, mainTable, this.Id, ref this.configObjects, dryRun);
diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs
index f05a724b..864f5488 100644
--- a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs
+++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs
@@ -102,7 +102,8 @@ public sealed record PluginConfigurationObject
PluginConfigurationObjectType.LLM_PROVIDER => (Settings.Provider.TryParseProviderTable(i, luaObjectTable, configPluginId, out var configurationObject) && configurationObject != Settings.Provider.NONE, configurationObject),
PluginConfigurationObjectType.CHAT_TEMPLATE => (ChatTemplate.TryParseChatTemplateTable(i, luaObjectTable, configPluginId, out var configurationObject) && configurationObject != ChatTemplate.NO_CHAT_TEMPLATE, configurationObject),
PluginConfigurationObjectType.PROFILE => (Profile.TryParseProfileTable(i, luaObjectTable, configPluginId, out var configurationObject) && configurationObject != Profile.NO_PROFILE, configurationObject),
-
+ PluginConfigurationObjectType.EMBEDDING_PROVIDER => (EmbeddingProvider.TryParseEmbeddingProviderTable(i, luaObjectTable, configPluginId, out var configurationObject) && configurationObject != EmbeddingProvider.NONE, configurationObject),
+
_ => (false, NoConfigurationObject.INSTANCE)
};
diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs
index 8990d499..14a5ab8e 100644
--- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs
+++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs
@@ -132,7 +132,11 @@ public static partial class PluginFactory
// Check LLM providers:
var wasConfigurationChanged = PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.LLM_PROVIDER, x => x.Providers, AVAILABLE_PLUGINS, configObjectList);
-
+
+ // Check embedding providers:
+ if(PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.EMBEDDING_PROVIDER, x => x.EmbeddingProviders, AVAILABLE_PLUGINS, configObjectList))
+ wasConfigurationChanged = true;
+
// Check chat templates:
if(PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.CHAT_TEMPLATE, x => x.ChatTemplates, AVAILABLE_PLUGINS, configObjectList))
wasConfigurationChanged = true;
diff --git a/app/MindWork AI Studio/wwwroot/changelog/v26.1.1.md b/app/MindWork AI Studio/wwwroot/changelog/v26.1.1.md
index 49ec1f85..9cfe8a68 100644
--- a/app/MindWork AI Studio/wwwroot/changelog/v26.1.1.md
+++ b/app/MindWork AI Studio/wwwroot/changelog/v26.1.1.md
@@ -3,5 +3,6 @@
- Added the option to use source code files in chats and document analysis. This supports software development workflows.
- Added a preview feature that lets you record your own voice in preparation for the transcription feature. The feature remains in development and appears only when the preview feature is enabled.
- Added the option to configure transcription providers (for example, using Whisper models). As usual, there can be local as well as cloud models configured. This option is part of the transcription preview and remains hidden until the preview is activated or the feature gets released.
+- Added the option to configure embedding providers through a config plugin and distribute them within an organization.
- Improved the app versioning. Starting in 2026, each version number includes the year, followed by the month. The last digit shows the release number for that month. For example, version `26.1.1` is the first release in January 2026.
- Fixed a bug in the profile selection where the "Use no profile" entry could not be localized, causing English text to appear in languages such as German. This behavior has now been fixed.