Added importing of configurations exported from AI Studio (#1007)

Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
This commit is contained in:
Peer HogeterpandThorsten Sommer authored and GitHub committed 2026-10-16 17:22:23 +02:00
1 parent f8c0232cfd
commit 22065783c2
49 files changed
+3257 -263

No files matched your search

@@ -1,5 +1,7 @@
using System.Linq.Expressions;
using AIStudio.Tools.PluginSystem;
namespace AIStudio.Settings.DataModel;
public sealed class DataApp(Expression<Func<Data, DataApp>>? configSelection = null)
@@ -199,6 +201,77 @@ public sealed class DataApp(Expression<Func<Data, DataApp>>? configSelection = n
/// </remarks>
public bool AllowUserToAddMailbox { get; set; } = ManagedConfiguration.Register(configSelection, n => n.AllowUserToAddMailbox, true);
/// <summary>
/// Should the user be allowed to import configurations which others exported?
/// </summary>
/// <remarks>
/// The master switch of the import: the switch of each type allows its import only while this
/// one does as well. Use CanImportConfigurationSnippet rather than reading the switches one by one.
/// </remarks>
public bool AllowUserToImportConfigurationSnippets { get; set; } = ManagedConfiguration.Register(configSelection, n => n.AllowUserToImportConfigurationSnippets, true);
/// <summary>
/// Should the user be allowed to import profiles?
/// </summary>
public bool AllowUserToImportProfile { get; set; } = ManagedConfiguration.Register(configSelection, n => n.AllowUserToImportProfile, true);
/// <summary>
/// Should the user be allowed to import LLM providers?
/// </summary>
/// <remarks>
/// An import adds a provider, so AllowUserToAddProvider and AllowUserToAddLLMProvider must allow it as well.
/// </remarks>
public bool AllowUserToImportLLMProvider { get; set; } = ManagedConfiguration.Register(configSelection, n => n.AllowUserToImportLLMProvider, true);
/// <summary>
/// Should the user be allowed to import embedding providers?
/// </summary>
/// <remarks>
/// An import adds a provider, so AllowUserToAddProvider and AllowUserToAddEmbeddingProvider must allow it as well.
/// </remarks>
public bool AllowUserToImportEmbeddingProvider { get; set; } = ManagedConfiguration.Register(configSelection, n => n.AllowUserToImportEmbeddingProvider, true);
/// <summary>
/// Should the user be allowed to import transcription providers?
/// </summary>
/// <remarks>
/// An import adds a provider, so AllowUserToAddProvider and AllowUserToAddTranscriptionProvider must allow it as well.
/// </remarks>
public bool AllowUserToImportTranscriptionProvider { get; set; } = ManagedConfiguration.Register(configSelection, n => n.AllowUserToImportTranscriptionProvider, true);
/// <summary>
/// Should the user be allowed to import chat templates?
/// </summary>
public bool AllowUserToImportChatTemplate { get; set; } = ManagedConfiguration.Register(configSelection, n => n.AllowUserToImportChatTemplate, true);
/// <summary>
/// Should the user be allowed to import external data sources (ERI servers)?
/// </summary>
public bool AllowUserToImportERIDataSource { get; set; } = ManagedConfiguration.Register(configSelection, n => n.AllowUserToImportERIDataSource, true);
/// <summary>
/// Should the user be allowed to import document analysis policies?
/// </summary>
public bool AllowUserToImportDocumentAnalysisPolicy { get; set; } = ManagedConfiguration.Register(configSelection, n => n.AllowUserToImportDocumentAnalysisPolicy, true);
/// <summary>
/// May the user import a configuration of this type, which somebody else exported?
/// </summary>
/// <param name="type">The type of configuration object to import.</param>
/// <returns>True when the master switch and the switch of the type allow it, and for providers also the permission to add one.</returns>
public bool CanImportConfigurationSnippet(PluginConfigurationObjectType type) => this.AllowUserToImportConfigurationSnippets && type switch
{
PluginConfigurationObjectType.PROFILE => this.AllowUserToImportProfile,
PluginConfigurationObjectType.LLM_PROVIDER => this.AllowUserToImportLLMProvider && this.AllowUserToAddProvider && this.AllowUserToAddLLMProvider,
PluginConfigurationObjectType.EMBEDDING_PROVIDER => this.AllowUserToImportEmbeddingProvider && this.AllowUserToAddProvider && this.AllowUserToAddEmbeddingProvider,
PluginConfigurationObjectType.TRANSCRIPTION_PROVIDER => this.AllowUserToImportTranscriptionProvider && this.AllowUserToAddProvider && this.AllowUserToAddTranscriptionProvider,
PluginConfigurationObjectType.CHAT_TEMPLATE => this.AllowUserToImportChatTemplate,
PluginConfigurationObjectType.DATA_SOURCE => this.AllowUserToImportERIDataSource,
PluginConfigurationObjectType.DOCUMENT_ANALYSIS_POLICY => this.AllowUserToImportDocumentAnalysisPolicy,
_ => false,
};
/// <summary>
/// Should the user be allowed to import plugin archives from disk?
/// </summary>
@@ -251,4 +324,4 @@ public sealed class DataApp(Expression<Func<Data, DataApp>>? configSelection = n
/// List of assistants that should be hidden from the UI.
/// </summary>
public HashSet<ConfigurableAssistant> HiddenAssistants { get; set; } = ManagedConfiguration.Register(configSelection, n => n.HiddenAssistants, []);
}
}
@@ -1,7 +1,9 @@
using AIStudio.Provider;
using AIStudio.Tools.PluginSystem;
using Lua;
using SharedTools;
using LuaTable = Lua.LuaTable;
namespace AIStudio.Settings.DataModel;
@@ -177,7 +179,56 @@ public sealed record DataDocumentAnalysisPolicy : ConfigurationBaseObject
IsEnterpriseConfiguration = true,
EnterpriseConfigurationPluginId = configPluginId,
};
return true;
}
/// <summary>
/// Exports the policy as a Lua configuration section.
/// </summary>
/// <remarks>
/// Unlike the other exports, this one keeps the ID of the policy.
/// </remarks>
/// <returns>A Lua configuration section string.</returns>
public string ExportAsConfigurationSection()
{
var preselectedProvider = string.IsNullOrWhiteSpace(this.PreselectedProvider) ? string.Empty : this.PreselectedProvider;
var preselectedProfile = string.IsNullOrWhiteSpace(this.PreselectedProfile) ? string.Empty : this.PreselectedProfile;
var id = string.IsNullOrWhiteSpace(this.Id) ? Guid.NewGuid().ToString() : this.Id;
var allowedToolIds = string.Join(", ", this.AllowedToolIds.OrderBy(x => x, StringComparer.Ordinal).Select(x => LuaTools.ToLuaStringLiteral(x)));
return $$"""
CONFIG["DOCUMENT_ANALYSIS_POLICIES"][#CONFIG["DOCUMENT_ANALYSIS_POLICIES"]+1] = {
["Id"] = "{{id}}",
["PolicyName"] = {{LuaTools.ToLuaStringLiteral(this.PolicyName.Trim())}},
["PolicyDescription"] = {{LuaTools.ToLuaStringLiteral(this.PolicyDescription.Trim())}},
["AnalysisRules"] = {{LuaTools.ToLuaStringLiteral(this.AnalysisRules.Trim(), forceLongString: true)}},
["OutputRules"] = {{LuaTools.ToLuaStringLiteral(this.OutputRules.Trim(), forceLongString: true)}},
-- Optional: minimum provider confidence required for this policy.
-- Allowed values are: NONE, VERY_LOW, LOW, MODERATE, MEDIUM, HIGH
["MinimumProviderConfidence"] = "{{this.MinimumProviderConfidence}}",
-- The tools an analysis with this policy may use, by tool ID.
-- This is a limit, not a preselection: a tool which is not listed here cannot
-- be used for this policy. An empty list means no tools. A listed tool must
-- still meet the confidence requirements of the provider in use.
["AllowedToolIds"] = { {{allowedToolIds}} },
-- Optional: preselect a provider or profile by ID.
-- The IDs must exist in CONFIG["LLM_PROVIDERS"] or CONFIG["PROFILES"].
["PreselectedProvider"] = "{{preselectedProvider}}",
["PreselectedProfile"] = "{{preselectedProfile}}",
-- Optional: hide the policy definition section in the UI.
-- When set to true, users will only see the document selection interface
-- and cannot view or modify the policy settings.
-- This is useful for enterprise configurations where policy details should remain hidden.
-- Allowed values are: true, false (default: false)
["HidePolicyDefinition"] = {{this.HidePolicyDefinition.ToString().ToLowerInvariant()}},
}
""";
}
}
@@ -24,6 +24,11 @@ public readonly record struct DataSourceERI_V1 : IERIDataSource
{
private static readonly ILogger<DataSourceERI_V1> LOGGER = Program.LOGGER_FACTORY.CreateLogger<DataSourceERI_V1>();
/// <summary>
/// What an export writes as the shared username when the data source has none to export.
/// </summary>
public const string SHARED_USERNAME_PLACEHOLDER = "<shared username>";
public DataSourceERI_V1()
{
}
@@ -319,7 +324,7 @@ public readonly record struct DataSourceERI_V1 : IERIDataSource
switch (this.AuthMethod)
{
case AuthMethod.TOKEN:
secretLine = CreateSecretLine("Token", encryptedSecret);
secretLine = CreateSecretLine(GetSecretFieldName(AuthMethod.TOKEN), encryptedSecret);
break;
case AuthMethod.USERNAME_PASSWORD:
@@ -332,13 +337,13 @@ public readonly record struct DataSourceERI_V1 : IERIDataSource
if (usernamePasswordMode is DataSourceERIUsernamePasswordMode.SHARED_USERNAME_AND_PASSWORD)
{
var username = string.IsNullOrWhiteSpace(this.Username) ? "<shared username>" : this.Username;
var username = string.IsNullOrWhiteSpace(this.Username) ? SHARED_USERNAME_PLACEHOLDER : this.Username;
usernameLine = $"""
["Username"] = "{LuaTools.EscapeLuaString(username)}",
""";
}
secretLine = CreateSecretLine("Password", encryptedSecret);
secretLine = CreateSecretLine(GetSecretFieldName(AuthMethod.USERNAME_PASSWORD), encryptedSecret);
break;
}
@@ -360,15 +365,21 @@ public readonly record struct DataSourceERI_V1 : IERIDataSource
""";
}
/// <summary>
/// The field of a configuration which holds the encrypted secret of an auth method.
/// </summary>
/// <param name="authMethod">How the data source signs in.</param>
/// <returns>The name of the field, or an empty string for an auth method without a secret.</returns>
public static string GetSecretFieldName(AuthMethod authMethod) => authMethod switch
{
AuthMethod.TOKEN => "Token",
AuthMethod.USERNAME_PASSWORD => "Password",
_ => string.Empty,
};
private static bool TryQueueEnterpriseSecret(int idx, LuaTable table, Guid configPluginId, DataSourceERI_V1 dataSource, bool dryRun)
{
var secretFieldName = dataSource.AuthMethod switch
{
AuthMethod.TOKEN => "Token",
AuthMethod.USERNAME_PASSWORD => "Password",
_ => string.Empty,
};
var secretFieldName = GetSecretFieldName(dataSource.AuthMethod);
if (string.IsNullOrWhiteSpace(secretFieldName))
return true;
@@ -428,4 +439,4 @@ public readonly record struct DataSourceERI_V1 : IERIDataSource
var cleanedHostname = hostname.Trim();
return cleanedHostname.EndsWith('/') ? cleanedHostname[..^1] : cleanedHostname;
}
}
}