Merge branch 'main' into chunk-data

This commit is contained in:
Paul Koudelka committed 2026-07-01 09:54:12 +02:00
commit 2e06af13e0
133 files changed
+3704 -968

No files matched your search

@@ -151,7 +151,7 @@ public record ConfigMeta<TClass, TValue> : ConfigMetaBase
/// </summary>
private void Reset()
{
var configInstance = this.ConfigSelection.Compile().Invoke(SETTINGS_MANAGER.ConfigurationData);
var configInstance = this.ConfigSelection.Compile().Invoke(SettingsManagerAccess.ConfigurationData);
var memberExpression = this.PropertyExpression.GetMemberExpression();
if (memberExpression.Member is System.Reflection.PropertyInfo propertyInfo)
propertyInfo.SetValue(configInstance, this.Default);
@@ -163,7 +163,7 @@ public record ConfigMeta<TClass, TValue> : ConfigMetaBase
/// <param name="value">The value to set for the configuration property.</param>
public void SetValue(TValue value)
{
var configInstance = this.ConfigSelection.Compile().Invoke(SETTINGS_MANAGER.ConfigurationData);
var configInstance = this.ConfigSelection.Compile().Invoke(SettingsManagerAccess.ConfigurationData);
var memberExpression = this.PropertyExpression.GetMemberExpression();
if (memberExpression.Member is System.Reflection.PropertyInfo propertyInfo)
propertyInfo.SetValue(configInstance, value);
@@ -174,7 +174,7 @@ public record ConfigMeta<TClass, TValue> : ConfigMetaBase
/// </summary>
public TValue GetValue()
{
var configInstance = this.ConfigSelection.Compile().Invoke(SETTINGS_MANAGER.ConfigurationData);
var configInstance = this.ConfigSelection.Compile().Invoke(SettingsManagerAccess.ConfigurationData);
var memberExpression = this.PropertyExpression.GetMemberExpression();
if (memberExpression.Member is System.Reflection.PropertyInfo propertyInfo && propertyInfo.GetValue(configInstance) is TValue value)
return value;
@@ -2,5 +2,5 @@ namespace AIStudio.Settings;
public abstract record ConfigMetaBase : IConfig
{
protected static readonly SettingsManager SETTINGS_MANAGER = Program.SERVICE_PROVIDER.GetRequiredService<SettingsManager>();
protected static SettingsManager SettingsManagerAccess => Program.SERVICE_PROVIDER.GetRequiredService<SettingsManager>();
}
@@ -271,8 +271,8 @@ public static class ConfigurationSelectDataFactory
public static IEnumerable<ConfigurationSelectData<ConfidenceLevel>> GetConfidenceLevelsData(SettingsManager settingsManager, bool restrictToGlobalMinimum = false)
{
var minimumLevel = ConfidenceLevel.NONE;
if(restrictToGlobalMinimum && settingsManager.ConfigurationData.LLMProviders is { EnforceGlobalMinimumConfidence: true, GlobalMinimumConfidence: not ConfidenceLevel.NONE and not ConfidenceLevel.UNKNOWN })
minimumLevel = settingsManager.ConfigurationData.LLMProviders.GlobalMinimumConfidence;
if(restrictToGlobalMinimum && settingsManager.ConfigurationData.Confidence is { EnforceGlobalMinimumConfidence: true, GlobalMinimumConfidence: not ConfidenceLevel.NONE and not ConfidenceLevel.UNKNOWN })
minimumLevel = settingsManager.ConfigurationData.Confidence.GlobalMinimumConfidence;
foreach (var level in Enum.GetValues<ConfidenceLevel>())
{
@@ -11,7 +11,7 @@ public sealed class Data
/// The version of the settings file. Allows us to upgrade the settings
/// when a new version is available.
/// </summary>
public Version Version { get; init; } = Version.V5;
public Version Version { get; init; } = Version.V6;
/// <summary>
/// List of configured providers.
@@ -19,9 +19,14 @@ public sealed class Data
public List<Provider> Providers { get; init; } = [];
/// <summary>
/// Settings concerning the LLM providers.
/// Settings concerning confidence levels.
/// </summary>
public DataLLMProviders LLMProviders { get; init; } = new();
public DataConfidence Confidence { get; init; } = new(x => x.Confidence);
/// <summary>
/// Settings concerning data source security checks.
/// </summary>
public DataSourceSecuritySettings DataSourceSecurity { get; init; } = new(x => x.DataSourceSecurity);
/// <summary>
/// A collection of embedding providers configured.
@@ -100,7 +105,7 @@ public sealed class Data
public DataApp App { get; init; } = new(x => x.App);
public DataChat Chat { get; init; } = new();
public DataChat Chat { get; init; } = new(x => x.Chat);
public DataWorkspace Workspace { get; init; } = new();
@@ -57,6 +57,11 @@ public sealed class DataApp(Expression<Func<Data, DataApp>>? configSelection = n
/// </summary>
public StartPage StartPage { get; set; } = ManagedConfiguration.Register(configSelection, n => n.StartPage, StartPage.HOME);
/// <summary>
/// Should the built-in introduction be visible on the home page?
/// </summary>
public bool ShowIntroduction { get; set; } = ManagedConfiguration.Register(configSelection, n => n.ShowIntroduction, true);
/// <summary>
/// Should the quick start guide be visible on the home page?
/// </summary>
@@ -1,7 +1,16 @@
using System.Linq.Expressions;
namespace AIStudio.Settings.DataModel;
public sealed class DataChat
public sealed class DataChat(Expression<Func<Data, DataChat>>? configSelection = null)
{
/// <summary>
/// The default constructor for the JSON deserializer.
/// </summary>
public DataChat() : this(null)
{
}
/// <summary>
/// Shortcuts to send the input to the AI.
/// </summary>
@@ -25,22 +34,22 @@ public sealed class DataChat
/// <summary>
/// Preselect any chat options?
/// </summary>
public bool PreselectOptions { get; set; }
public bool PreselectOptions { get; set; } = ManagedConfiguration.Register(configSelection, n => n.PreselectOptions, false);
/// <summary>
/// Should we preselect a provider for the chat?
/// </summary>
public string PreselectedProvider { get; set; } = string.Empty;
public string PreselectedProvider { get; set; } = ManagedConfiguration.Register(configSelection, n => n.PreselectedProvider, string.Empty);
/// <summary>
/// Preselect a profile?
/// </summary>
public string PreselectedProfile { get; set; } = string.Empty;
public string PreselectedProfile { get; set; } = ManagedConfiguration.Register(configSelection, n => n.PreselectedProfile, string.Empty);
/// <summary>
/// Preselect a chat template?
/// </summary>
public string PreselectedChatTemplate { get; set; } = string.Empty;
public string PreselectedChatTemplate { get; set; } = ManagedConfiguration.Register(configSelection, n => n.PreselectedChatTemplate, string.Empty);
/// <summary>
/// Should we preselect data sources options for a created chat?
@@ -0,0 +1,40 @@
using System.Linq.Expressions;
using AIStudio.Provider;
namespace AIStudio.Settings.DataModel;
public sealed class DataConfidence(Expression<Func<Data, DataConfidence>>? configSelection = null)
{
/// <summary>
/// The default constructor for the JSON deserializer.
/// </summary>
public DataConfidence() : this(null)
{
}
/// <summary>
/// Should we enforce a global minimum confidence level?
/// </summary>
public bool EnforceGlobalMinimumConfidence { get; set; } = ManagedConfiguration.Register(configSelection, n => n.EnforceGlobalMinimumConfidence, false);
/// <summary>
/// The global minimum confidence level to enforce.
/// </summary>
public ConfidenceLevel GlobalMinimumConfidence { get; set; } = ManagedConfiguration.Register(configSelection, n => n.GlobalMinimumConfidence, ConfidenceLevel.NONE);
/// <summary>
/// Should we show the provider confidence level?
/// </summary>
public bool ShowProviderConfidence { get; set; } = ManagedConfiguration.Register(configSelection, n => n.ShowProviderConfidence, true);
/// <summary>
/// Which confidence scheme to use.
/// </summary>
public ConfidenceSchemes ConfidenceScheme { get; set; } = ManagedConfiguration.Register(configSelection, n => n.ConfidenceScheme, ConfidenceSchemes.TRUST_ALL);
/// <summary>
/// Provide custom confidence levels for each provider family.
/// </summary>
public Dictionary<LLMProviders, ConfidenceLevel> CustomConfidenceScheme { get; set; } = ManagedConfiguration.Register(configSelection, n => n.CustomConfidenceScheme, []);
}
@@ -0,0 +1,87 @@
using AIStudio.Tools.PluginSystem;
using Lua;
namespace AIStudio.Settings.DataModel;
public sealed record DataIntroduction : ILivePluginContent
{
private static readonly ILogger LOG = Program.LOGGER_FACTORY.CreateLogger<DataIntroduction>();
/// <summary>
/// The stable ID of the introduction.
/// </summary>
public string Id { get; private init; } = string.Empty;
/// <summary>
/// The ID of the enterprise configuration plugin that provides this introduction.
/// </summary>
public Guid EnterpriseConfigurationPluginId { get; private init; } = Guid.Empty;
/// <summary>
/// The title shown to the user.
/// </summary>
public string Title { get; private init; } = string.Empty;
/// <summary>
/// The configured version string shown to the user.
/// </summary>
public string VersionText { get; private init; } = string.Empty;
/// <summary>
/// The sort index used on the home page.
/// </summary>
public int Index { get; private init; } = 1;
/// <summary>
/// The Markdown content shown to the user.
/// </summary>
public string Markdown { get; private init; } = string.Empty;
public static bool TryParseConfiguration(int idx, LuaTable table, Guid configPluginId, out DataIntroduction introduction)
{
introduction = new DataIntroduction();
if (!table.TryGetValue("Id", out var idValue) || !idValue.TryRead<string>(out var idText) || !Guid.TryParse(idText, out var id))
{
LOG.LogWarning("The configured introduction {IntroductionIndex} does not contain a valid ID. The ID must be a valid GUID.", idx);
return false;
}
if (!table.TryGetValue("Title", out var titleValue) || !titleValue.TryRead<string>(out var title) || string.IsNullOrWhiteSpace(title))
{
LOG.LogWarning("The configured introduction {IntroductionIndex} does not contain a valid Title field.", idx);
return false;
}
if (!table.TryGetValue("Version", out var versionValue) || !versionValue.TryRead<string>(out var versionText) || string.IsNullOrWhiteSpace(versionText))
{
LOG.LogWarning("The configured introduction {IntroductionIndex} does not contain a valid Version field.", idx);
return false;
}
if (!table.TryGetValue("Markdown", out var markdownValue) || !markdownValue.TryRead<string>(out var markdown) || string.IsNullOrWhiteSpace(markdown))
{
LOG.LogWarning("The configured introduction {IntroductionIndex} does not contain a valid Markdown field.", idx);
return false;
}
var index = 1;
if (table.TryGetValue("Index", out var indexValue) && !indexValue.TryRead(out index))
{
LOG.LogWarning("The configured introduction {IntroductionIndex} does not contain a valid Index field. The Index must be an integer.", idx);
return false;
}
introduction = new DataIntroduction
{
Id = id.ToString(),
Title = title,
VersionText = versionText,
Index = index,
Markdown = AIStudio.Tools.Markdown.RemoveSharedIndentation(markdown),
EnterpriseConfigurationPluginId = configPluginId,
};
return true;
}
}
@@ -1,11 +1,13 @@
using System.Security.Cryptography;
using System.Text;
using AIStudio.Tools.PluginSystem;
using Lua;
namespace AIStudio.Settings.DataModel;
public sealed record DataMandatoryInfo
public sealed record DataMandatoryInfo : ILivePluginContent
{
private static readonly ILogger LOG = Program.LOGGER_FACTORY.CreateLogger<DataMandatoryInfo>();
@@ -1,8 +1,8 @@
using AIStudio.Provider;
namespace AIStudio.Settings.DataModel;
namespace AIStudio.Settings.DataModel.PreviousModels;
public sealed class DataLLMProviders
public sealed class DataLLMProvidersV5
{
/// <summary>
/// Should we enforce a global minimum confidence level?
@@ -25,7 +25,7 @@ public sealed class DataLLMProviders
public ConfidenceSchemes ConfidenceScheme { get; set; } = ConfidenceSchemes.TRUST_ALL;
/// <summary>
/// Provide custom confidence levels for each LLM provider.
/// Provide custom confidence levels for each provider family.
/// </summary>
public Dictionary<LLMProviders, ConfidenceLevel> CustomConfidenceScheme { get; set; } = new();
}
@@ -16,7 +16,7 @@ public sealed class DataV4
/// <summary>
/// Settings concerning the LLM providers.
/// </summary>
public DataLLMProviders LLMProviders { get; init; } = new();
public DataLLMProvidersV5 LLMProviders { get; init; } = new();
/// <summary>
/// List of configured profiles.
@@ -0,0 +1,149 @@
using AIStudio.Tools.PluginSystem.Assistants;
namespace AIStudio.Settings.DataModel.PreviousModels;
public sealed class DataV5
{
/// <summary>
/// The version of the settings file. Allows us to upgrade the settings
/// when a new version is available.
/// </summary>
public Version Version { get; init; } = Version.V5;
/// <summary>
/// List of configured providers.
/// </summary>
public List<AIStudio.Settings.Provider> Providers { get; init; } = [];
/// <summary>
/// Settings concerning the LLM providers.
/// </summary>
public DataLLMProvidersV5 LLMProviders { get; init; } = new();
/// <summary>
/// A collection of embedding providers configured.
/// </summary>
public List<EmbeddingProvider> EmbeddingProviders { get; init; } = [];
/// <summary>
/// A collection of speech providers configured.
/// </summary>
public List<TranscriptionProvider> TranscriptionProviders { get; init; } = [];
/// <summary>
/// A collection of data sources configured.
/// </summary>
public List<IDataSource> DataSources { get; set; } = [];
/// <summary>
/// List of configured profiles.
/// </summary>
public List<Profile> Profiles { get; init; } = [];
/// <summary>
/// List of configured chat templates.
/// </summary>
public List<ChatTemplate> ChatTemplates { get; init; } = [];
/// <summary>
/// List of enabled plugins.
/// </summary>
public List<Guid> EnabledPlugins { get; set; } = [];
/// <summary>
/// Metadata for managed settings that use a plugin-provided editable default.
/// </summary>
public Dictionary<string, ManagedEditableDefaultState> ManagedEditableDefaults { get; set; } = [];
/// <summary>
/// Cached audit results for assistant plugins.
/// </summary>
public List<PluginAssistantAudit> AssistantPluginAudits { get; set; } = [];
/// <summary>
/// The next provider number to use.
/// </summary>
public uint NextProviderNum { get; set; } = 1;
/// <summary>
/// The next embedding provider number to use.
/// </summary>
public uint NextEmbeddingNum { get; set; } = 1;
/// <summary>
/// The next transcription provider number to use.
/// </summary>
public uint NextTranscriptionNum { get; set; } = 1;
/// <summary>
/// The next data source number to use.
/// </summary>
public uint NextDataSourceNum { get; set; } = 1;
/// <summary>
/// The next profile number to use.
/// </summary>
public uint NextProfileNum { get; set; } = 1;
/// <summary>
/// The next chat template number to use.
/// </summary>
public uint NextChatTemplateNum { get; set; } = 1;
/// <summary>
/// The next document analysis policy number to use.
/// </summary>
public uint NextDocumentAnalysisPolicyNum { get; set; } = 1;
public DataApp App { get; init; } = new(x => x.App);
public DataChat Chat { get; init; } = new();
public DataWorkspace Workspace { get; init; } = new();
public DataIconFinder IconFinder { get; init; } = new();
public DataTranslation Translation { get; init; } = new();
public DataCoding Coding { get; init; } = new();
public DataERI ERI { get; init; } = new();
public DataDocumentAnalysis DocumentAnalysis { get; init; } = new();
public DataMandatoryInformation MandatoryInformation { get; init; } = new();
public DataTextSummarizer TextSummarizer { get; init; } = new();
public DataTextContentCleaner TextContentCleaner { get; init; } = new();
public DataAgentDataSourceSelection AgentDataSourceSelection { get; init; } = new();
public DataAgentRetrievalContextValidation AgentRetrievalContextValidation { get; init; } = new();
public DataAssistantPluginAudit AssistantPluginAudit { get; init; } = new(x => x.AssistantPluginAudit);
public DataAgenda Agenda { get; init; } = new();
public DataGrammarSpelling GrammarSpelling { get; init; } = new();
public DataRewriteImprove RewriteImprove { get; init; } = new();
public DataPromptOptimizer PromptOptimizer { get; init; } = new();
public DataEMail EMail { get; init; } = new();
public DataSlideBuilder SlideBuilder { get; init; } = new();
public DataLegalCheck LegalCheck { get; init; } = new();
public DataSynonyms Synonyms { get; init; } = new();
public DataMyTasks MyTasks { get; init; } = new();
public DataJobPostings JobPostings { get; init; } = new();
public DataBiasOfTheDay BiasOfTheDay { get; init; } = new();
public DataI18N I18N { get; init; } = new();
}
@@ -0,0 +1,20 @@
using System.Linq.Expressions;
using AIStudio.Settings.DataModel;
namespace AIStudio.Settings;
public sealed class DataSourceSecuritySettings(Expression<Func<Data, DataSourceSecuritySettings>>? configSelection = null)
{
/// <summary>
/// The default constructor for the JSON deserializer.
/// </summary>
public DataSourceSecuritySettings() : this(null)
{
}
/// <summary>
/// Provider instance IDs trusted by an organization for data-source security checks.
/// </summary>
public HashSet<string> TrustedProviderIds { get; set; } = ManagedConfiguration.Register(configSelection, n => n.TrustedProviderIds, []);
}
@@ -0,0 +1,52 @@
using AIStudio.Provider;
namespace AIStudio.Settings;
public static class DataSourceSecurityTrustExtensions
{
public static bool IsTrustedForDataSourceSecurityChecks(this Provider provider, SettingsManager settingsManager)
{
if (provider == Provider.NONE)
return false;
return provider.IsSelfHosted || provider.IsTrustedByConfiguration(settingsManager);
}
public static bool IsTrustedForDataSourceSecurityChecks(this EmbeddingProvider provider, SettingsManager settingsManager)
{
if (provider == EmbeddingProvider.NONE)
return false;
return provider.IsSelfHosted || provider.IsTrustedByConfiguration(settingsManager);
}
public static bool IsTrustedForDataSourceSecurityChecks(this TranscriptionProvider provider, SettingsManager settingsManager)
{
if (provider == TranscriptionProvider.NONE)
return false;
return provider.IsSelfHosted || provider.IsTrustedByConfiguration(settingsManager);
}
public static bool IsTrustedForDataSourceSecurityChecks(this IProvider provider, SettingsManager settingsManager)
{
if (provider is NoProvider)
return false;
return provider.Provider is LLMProviders.SELF_HOSTED || IsTrustedProviderId(provider.ConfiguredProviderId, settingsManager);
}
public static bool IsTrustedByConfiguration(this Provider provider, SettingsManager settingsManager) => IsTrustedProviderId(provider.Id, settingsManager);
public static bool IsTrustedByConfiguration(this EmbeddingProvider provider, SettingsManager settingsManager) => IsTrustedProviderId(provider.Id, settingsManager);
public static bool IsTrustedByConfiguration(this TranscriptionProvider provider, SettingsManager settingsManager) => IsTrustedProviderId(provider.Id, settingsManager);
private static bool IsTrustedProviderId(string providerId, SettingsManager settingsManager)
{
if (string.IsNullOrWhiteSpace(providerId))
return false;
return settingsManager.ConfigurationData.DataSourceSecurity.TrustedProviderIds.Any(id => string.Equals(id, providerId, StringComparison.OrdinalIgnoreCase));
}
}
@@ -45,7 +45,7 @@ public sealed record EmbeddingProvider(
/// <inheritdoc />
[JsonIgnore]
public string SecretId => this.IsEnterpriseConfiguration ? $"{ISecretId.ENTERPRISE_KEY_PREFIX}::{this.UsedLLMProvider.ToName()}" : this.UsedLLMProvider.ToName();
public string SecretId => this.IsEnterpriseConfiguration ? $"{ISecretId.ENTERPRISE_KEY_PREFIX}::{this.UsedLLMProvider.ToSecretId()}" : this.UsedLLMProvider.ToSecretId();
/// <inheritdoc />
[JsonIgnore]
@@ -134,7 +134,7 @@ public sealed record EmbeddingProvider(
{
// Queue the API key for storage in the OS keyring:
PendingEnterpriseApiKeys.Add(new(
$"{ISecretId.ENTERPRISE_KEY_PREFIX}::{usedLLMProvider.ToName()}",
$"{ISecretId.ENTERPRISE_KEY_PREFIX}::{usedLLMProvider.ToSecretId()}",
name,
decryptedApiKey,
SecretStoreType.EMBEDDING_PROVIDER));
@@ -38,14 +38,14 @@ public static partial class ManagedConfiguration
//
// Handle configured enum values
//
// Check if that configuration was registered:
if(!TryGet(configSelection, propertyExpression, out var configMeta))
return false;
var successful = false;
var configuredValue = configMeta.Default;
// Step 1 -- try to read the Lua value out of the Lua table:
if(settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredEnumValue))
{
@@ -60,7 +60,7 @@ public static partial class ManagedConfiguration
}
}
}
if(dryRun)
return successful;
@@ -98,14 +98,14 @@ public static partial class ManagedConfiguration
//
// Handle configured ISpanParsable values
//
// Check if that configuration was registered:
if(!TryGet(configSelection, propertyExpression, out var configMeta))
return false;
var successful = false;
var configuredValue = configMeta.Default;
// Step 1 -- try to read the Lua value out of the Lua table:
if (settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredLuaValue))
{
@@ -119,7 +119,7 @@ public static partial class ManagedConfiguration
successful = true;
}
}
// Step 2b -- try to read the Lua value:
if(configuredLuaValue.TryRead<TValue>(out var configuredLuaValueInstance))
{
@@ -135,7 +135,7 @@ public static partial class ManagedConfiguration
var managedMode = ReadManagedConfigurationMode(propertyExpression, settings);
return HandleParsedScalarValue(configPluginId, dryRun, successful, configMeta, configuredValue, managedMode, settingName);
}
/// <summary>
/// Attempts to process the configuration settings from a Lua table for string values.
/// </summary>
@@ -189,14 +189,14 @@ public static partial class ManagedConfiguration
//
// Handle configured string values
//
// Check if that configuration was registered:
if(!TryGet(configSelection, propertyExpression, out var configMeta))
return false;
var successful = false;
var configuredValue = configMeta.Default;
// Step 1 -- try to read the Lua value out of the Lua table:
if(settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredTextValue))
{
@@ -210,7 +210,7 @@ public static partial class ManagedConfiguration
successful = Guid.TryParse(configuredText, out var id);
configuredValue = successful ? id.ToString().ToLowerInvariant() : configuredText;
break;
// Case: the read string is just a string:
case string:
configuredValue = configuredText;
@@ -219,7 +219,7 @@ public static partial class ManagedConfiguration
}
}
}
var settingName = SettingName(propertyExpression);
var managedMode = ReadManagedConfigurationMode(propertyExpression, settings);
return HandleParsedScalarValue(configPluginId, dryRun, successful, configMeta, configuredValue, managedMode, settingName);
@@ -273,13 +273,13 @@ public static partial class ManagedConfiguration
// Determine the length of the Lua table and prepare a list to hold the parsed values:
var len = valueTable.ArrayLength;
var list = new List<TValue>(len);
// Iterate over each entry in the Lua table:
for (var index = 1; index <= len; index++)
{
// Retrieve the Lua value at the current index:
var value = valueTable[index];
// Step 2a -- try to read the Lua value as a string:
if (value.Type is LuaValueType.String && value.TryRead<string>(out var configuredLuaValueText))
{
@@ -304,7 +304,7 @@ public static partial class ManagedConfiguration
}
// ReSharper restore MethodOverloadWithOptionalParameter
/// <summary>
/// Attempts to process the configuration settings from a Lua table for enum list types.
/// </summary>
@@ -333,14 +333,14 @@ public static partial class ManagedConfiguration
//
// Handle configured enum lists
//
// Check if that configuration was registered:
if(!TryGet(configSelection, propertyExpression, out var configMeta))
return false;
var successful = false;
var configuredValue = configMeta.Default;
// Step 1 -- try to read the Lua value (we expect a table) out of the Lua table:
if (settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredLuaList) &&
configuredLuaList.Type is LuaValueType.Table &&
@@ -349,13 +349,13 @@ public static partial class ManagedConfiguration
// Determine the length of the Lua table and prepare a list to hold the parsed values:
var len = valueTable.ArrayLength;
var list = new List<TValue>(len);
// Iterate over each entry in the Lua table:
for (var index = 1; index <= len; index++)
{
// Retrieve the Lua value at the current index:
var value = valueTable[index];
// Step 2 -- try to read the Lua value as a string:
if (value.Type is LuaValueType.String && value.TryRead<string>(out var configuredLuaValueText))
{
@@ -364,17 +364,17 @@ public static partial class ManagedConfiguration
list.Add((TValue)configuredEnum);
}
}
configuredValue = list;
successful = true;
}
if(dryRun)
return successful;
return HandleParsedValue(configPluginId, dryRun, successful, configMeta, configuredValue);
}
/// <summary>
/// Attempts to process the configuration settings from a Lua table for string list types.
/// </summary>
@@ -400,14 +400,14 @@ public static partial class ManagedConfiguration
//
// Handle configured string lists
//
// Check if that configuration was registered:
if(!TryGet(configSelection, propertyExpression, out var configMeta))
return false;
var successful = false;
var configuredValue = configMeta.Default;
// Step 1 -- try to read the Lua value (we expect a table) out of the Lua table:
if (settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredLuaList) &&
configuredLuaList.Type is LuaValueType.Table &&
@@ -416,25 +416,25 @@ public static partial class ManagedConfiguration
// Determine the length of the Lua table and prepare a list to hold the parsed values:
var len = valueTable.ArrayLength;
var list = new List<string>(len);
// Iterate over each entry in the Lua table:
for (var index = 1; index <= len; index++)
{
// Retrieve the Lua value at the current index:
var value = valueTable[index];
// Step 2 -- try to read the Lua value as a string:
if (value.Type is LuaValueType.String && value.TryRead<string>(out var configuredLuaValueText))
list.Add(configuredLuaValueText);
}
configuredValue = list;
successful = true;
}
if(dryRun)
return successful;
return HandleParsedValue(configPluginId, dryRun, successful, configMeta, configuredValue);
}
@@ -486,13 +486,13 @@ public static partial class ManagedConfiguration
// Determine the length of the Lua table and prepare a set to hold the parsed values:
var len = valueTable.ArrayLength;
var set = new HashSet<TValue>(len);
// Iterate over each entry in the Lua table:
for (var index = 1; index <= len; index++)
{
// Retrieve the Lua value at the current index:
var value = valueTable[index];
// Step 2a -- try to read the Lua value as a string:
if (value.Type is LuaValueType.String && value.TryRead<string>(out var configuredLuaValueText))
{
@@ -517,7 +517,7 @@ public static partial class ManagedConfiguration
}
// ReSharper restore MethodOverloadWithOptionalParameter
/// <summary>
/// Attempts to process the configuration settings from a Lua table for enum set types.
/// </summary>
@@ -546,14 +546,14 @@ public static partial class ManagedConfiguration
//
// Handle configured enum sets
//
// Check if that configuration was registered:
if(!TryGet(configSelection, propertyExpression, out var configMeta))
return false;
var successful = false;
var configuredValue = configMeta.Default;
// Step 1 -- try to read the Lua value (we expect a table) out of the Lua table:
if (settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredLuaList) &&
configuredLuaList.Type is LuaValueType.Table &&
@@ -562,13 +562,13 @@ public static partial class ManagedConfiguration
// Determine the length of the Lua table and prepare a set to hold the parsed values:
var len = valueTable.ArrayLength;
var set = new HashSet<TValue>(len);
// Iterate over each entry in the Lua table:
for (var index = 1; index <= len; index++)
{
// Retrieve the Lua value at the current index:
var value = valueTable[index];
// Step 2 -- try to read the Lua value as a string:
if (value.Type is LuaValueType.String && value.TryRead<string>(out var configuredLuaValueText))
{
@@ -577,14 +577,14 @@ public static partial class ManagedConfiguration
set.Add((TValue)configuredEnum);
}
}
configuredValue = set;
successful = true;
}
if(dryRun)
return successful;
return HandleParsedValue(configPluginId, dryRun, successful, configMeta, configuredValue);
}
@@ -629,13 +629,13 @@ public static partial class ManagedConfiguration
// Determine the length of the Lua table and prepare a set to hold the parsed values:
var len = valueTable.ArrayLength;
var set = new HashSet<TValue>(len);
// Iterate over each entry in the Lua table:
for (var index = 1; index <= len; index++)
{
// Retrieve the Lua value at the current index:
var value = valueTable[index];
// Step 2 -- try to read the Lua value as a string:
if (value.Type is LuaValueType.String && value.TryRead<string>(out var configuredLuaValueText))
{
@@ -654,7 +654,7 @@ public static partial class ManagedConfiguration
if (successful)
{
var configInstance = configSelection.Compile().Invoke(SETTINGS_MANAGER.ConfigurationData);
var configInstance = configSelection.Compile().Invoke(SettingsManagerAccess.ConfigurationData);
var currentValue = propertyExpression.Compile().Invoke(configInstance);
var merged = new HashSet<TValue>(currentValue);
merged.UnionWith(configuredValue);
@@ -671,7 +671,7 @@ public static partial class ManagedConfiguration
return successful;
}
/// <summary>
/// Attempts to process the configuration settings from a Lua table for string set types.
/// </summary>
@@ -697,14 +697,14 @@ public static partial class ManagedConfiguration
//
// Handle configured string sets
//
// Check if that configuration was registered:
if(!TryGet(configSelection, propertyExpression, out var configMeta))
return false;
var successful = false;
var configuredValue = configMeta.Default;
// Step 1 -- try to read the Lua value (we expect a table) out of the Lua table:
if (settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredLuaList) &&
configuredLuaList.Type is LuaValueType.Table &&
@@ -713,28 +713,28 @@ public static partial class ManagedConfiguration
// Determine the length of the Lua table and prepare a set to hold the parsed values:
var len = valueTable.ArrayLength;
var set = new HashSet<string>(len);
// Iterate over each entry in the Lua table:
for (var index = 1; index <= len; index++)
{
// Retrieve the Lua value at the current index:
var value = valueTable[index];
// Step 2 -- try to read the Lua value as a string:
if (value.Type is LuaValueType.String && value.TryRead<string>(out var configuredLuaValueText))
set.Add(configuredLuaValueText);
}
configuredValue = set;
successful = true;
}
if(dryRun)
return successful;
return HandleParsedValue(configPluginId, dryRun, successful, configMeta, configuredValue);
}
/// <summary>
/// Attempts to process the configuration settings from a Lua table for string dictionary types.
/// </summary>
@@ -760,14 +760,14 @@ public static partial class ManagedConfiguration
//
// Handle configured string dictionaries (both keys and values are strings)
//
// Check if that configuration was registered:
if(!TryGet(configSelection, propertyExpression, out var configMeta))
return false;
var successful = false;
var configuredValue = configMeta.Default;
// Step 1 -- try to read the Lua value (we expect a table) out of the Lua table:
if (settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredLuaList) &&
configuredLuaList.Type is LuaValueType.Table &&
@@ -778,7 +778,7 @@ public static partial class ManagedConfiguration
var len = valueTable.HashMapCount;
if (len > 0)
configuredValue.Clear();
// In order to iterate over all key-value pairs in the Lua table, we have to use TryGetNext.
// Thus, we initialize the previous key variable to Nil and keep calling TryGetNext until
// there are no more pairs:
@@ -787,25 +787,101 @@ public static partial class ManagedConfiguration
{
// Update the previous key for the next iteration:
previousKey = pair.Key;
// Try to read both the key and the value as strings:
var hadKey = pair.Key.TryRead<string>(out var key);
var hadValue = pair.Value.TryRead<string>(out var value);
// If both key and value were read successfully, add them to the dictionary:
if (hadKey && hadValue)
configuredValue[key] = value;
}
successful = true;
}
if(dryRun)
return successful;
return HandleParsedValue(configPluginId, dryRun, successful, configMeta, configuredValue);
}
/// <summary>
/// Attempts to process the configuration settings from a Lua table for enum dictionary types.
/// </summary>
/// <remarks>
/// When the configuration is successfully processed, it updates the configuration metadata with the configured value.
/// Furthermore, it applies the configured managed state to the provided configuration plugin ID.
/// The setting's value is set to the configured value when locked or when the editable default should apply.
/// </remarks>
/// <param name="configPluginId">The ID of the related configuration plugin.</param>
/// <param name="settings">The Lua table containing the settings to process.</param>
/// <param name="configSelection">The expression to select the configuration class.</param>
/// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
/// <param name="dryRun">When true, the method will not apply any changes but only check if the configuration can be read.</param>
/// <typeparam name="TClass">The type of the configuration class.</typeparam>
/// <typeparam name="TKey">The enum type of the dictionary keys.</typeparam>
/// <typeparam name="TValue">The enum type of the dictionary values.</typeparam>
/// <returns>True when the configuration was successfully processed, otherwise false.</returns>
public static bool TryProcessConfiguration<TClass, TKey, TValue>(
Expression<Func<Data, TClass>> configSelection,
Expression<Func<TClass, Dictionary<TKey, TValue>>> propertyExpression,
Guid configPluginId,
LuaTable settings,
bool dryRun)
where TKey : struct, Enum
where TValue : struct, Enum
{
//
// Handle configured enum dictionaries (Lua keys and values are strings)
//
// Check if that configuration was registered:
if(!TryGet(configSelection, propertyExpression, out var configMeta))
return false;
var successful = false;
var configuredValue = new Dictionary<TKey, TValue>(configMeta.Default);
// Step 1 -- try to read the Lua value (we expect a table) out of the Lua table:
if (settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredLuaList) &&
configuredLuaList.Type is LuaValueType.Table &&
configuredLuaList.TryRead<LuaTable>(out var valueTable))
{
configuredValue.Clear();
// In order to iterate over all key-value pairs in the Lua table, we have to use TryGetNext.
// Thus, we initialize the previous key variable to Nil and keep calling TryGetNext until
// there are no more pairs:
var previousKey = LuaValue.Nil;
while(valueTable.TryGetNext(previousKey, out var pair))
{
// Update the previous key for the next iteration:
previousKey = pair.Key;
// Try to read both the key and the value as strings:
var hadKey = pair.Key.TryRead<string>(out var keyText);
var hadValue = pair.Value.TryRead<string>(out var valueText);
// If both key and value were read successfully, parse and add them to the dictionary:
if (hadKey
&& hadValue
&& Enum.TryParse<TKey>(keyText, true, out var key)
&& Enum.TryParse<TValue>(valueText, true, out var value))
configuredValue[key] = value;
}
successful = true;
}
if(dryRun)
return successful;
var settingName = SettingName(propertyExpression);
var managedMode = ReadManagedConfigurationMode(propertyExpression, settings);
return HandleParsedScalarValue(configPluginId, dryRun, successful, configMeta, configuredValue, managedMode, settingName);
}
/// <summary>
/// Handles the parsed configuration value based on whether the parsing was successful and whether it's a dry run.
/// </summary>
@@ -826,14 +902,14 @@ public static partial class ManagedConfiguration
{
if(dryRun)
return successful;
switch (successful)
{
case true:
//
// Case: the setting was configured, and we could read the value successfully.
//
// Set the configured value and lock the configuration:
configMeta.SetValue(configuredValue);
configMeta.LockConfiguration(configPluginId);
@@ -852,7 +928,7 @@ public static partial class ManagedConfiguration
//
configMeta.ResetLockedConfiguration();
break;
case false:
//
// Case: the setting was not configured, or we could not read the value successfully.
@@ -946,8 +1022,12 @@ public static partial class ManagedConfiguration
{
null => string.Empty,
string text => text,
System.Collections.IDictionary dictionary => string.Join(";", dictionary.Keys
.Cast<object>()
.OrderBy(key => key.ToString(), StringComparer.Ordinal)
.Select(key => $"{key}:{dictionary[key]}")),
IFormattable formattable => formattable.ToString(null, CultureInfo.InvariantCulture),
_ => value.ToString() ?? string.Empty,
};
}
@@ -66,13 +66,13 @@ public static partial class ManagedConfiguration
// we ignore the register call and return the default value:
if(configSelection is null)
return defaultValue;
var configPath = Path(configSelection, propertyExpression);
// If the metadata already exists for this configuration path, we return the default value:
if (METADATA.ContainsKey(configPath))
return defaultValue;
// Not registered yet, so we register it now:
METADATA[configPath] = new ConfigMeta<TClass, string>(configSelection, propertyExpression)
{
@@ -104,13 +104,13 @@ public static partial class ManagedConfiguration
// we ignore the register call and return the default value:
if(configSelection is null)
return [defaultValue];
var configPath = Path(configSelection, propertyExpression);
// If the metadata already exists for this configuration path, we return the default value:
if (METADATA.ContainsKey(configPath))
return [defaultValue];
// Not registered yet, so we register it now:
METADATA[configPath] = new ConfigMeta<TClass, IList<TValue>>(configSelection, propertyExpression)
{
@@ -142,13 +142,13 @@ public static partial class ManagedConfiguration
// we ignore the register call and return the default value:
if(configSelection is null)
return [..defaultValues];
var configPath = Path(configSelection, propertyExpression);
// If the metadata already exists for this configuration path, we return the default value:
if (METADATA.ContainsKey(configPath))
return [..defaultValues];
// Not registered yet, so we register it now:
METADATA[configPath] = new ConfigMeta<TClass, IList<TValue>>(configSelection, propertyExpression)
{
@@ -179,13 +179,13 @@ public static partial class ManagedConfiguration
// we ignore the register call and return the default value:
if (configSelection is null)
return [defaultValue];
var configPath = Path(configSelection, propertyExpression);
// If the metadata already exists for this configuration path, we return the default value:
if (METADATA.ContainsKey(configPath))
return [defaultValue];
// Not registered yet, so we register it now:
METADATA[configPath] = new ConfigMeta<TClass, ISet<TValue>>(configSelection, propertyExpression)
{
@@ -217,13 +217,13 @@ public static partial class ManagedConfiguration
// we ignore the register call and return the default value:
if (configSelection is null)
return [..defaultValues];
var configPath = Path(configSelection, propertyExpression);
// If the metadata already exists for this configuration path, we return the default value:
if (METADATA.ContainsKey(configPath))
return [..defaultValues];
// Not registered yet, so we register it now:
METADATA[configPath] = new ConfigMeta<TClass, ISet<TValue>>(configSelection, propertyExpression)
{
@@ -268,7 +268,48 @@ public static partial class ManagedConfiguration
{
Default = defaultValues,
};
return defaultValues;
}
/// <summary>
/// Registers a configuration setting with a default dictionary of enum key-value pairs.
/// </summary>
/// <remarks>
/// When the method is invoked with a null configSelection, the configuration path
/// is ignored, and the specified default values are returned without registration.
/// </remarks>
/// <param name="configSelection">The expression that selects the configuration class from the root Data model.</param>
/// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
/// <param name="defaultValues">The default dictionary of values to use when the setting is not configured.</param>
/// <typeparam name="TClass">The type of the configuration class from which the property is selected.</typeparam>
/// <typeparam name="TKey">The enum type of the dictionary keys.</typeparam>
/// <typeparam name="TValue">The enum type of the dictionary values.</typeparam>
/// <returns>A dictionary containing the default values.</returns>
public static Dictionary<TKey, TValue> Register<TClass, TKey, TValue>(
Expression<Func<Data, TClass>>? configSelection,
Expression<Func<TClass, Dictionary<TKey, TValue>>> propertyExpression,
Dictionary<TKey, TValue> defaultValues)
where TKey : struct, Enum
where TValue : struct, Enum
{
// When called from the JSON deserializer by using the standard constructor,
// we ignore the register call and return the default value:
if (configSelection is null)
return new();
var configPath = Path(configSelection, propertyExpression);
// If the metadata already exists for this configuration path, we return the default value:
if (METADATA.ContainsKey(configPath))
return defaultValues;
// Not registered yet, so we register it now:
METADATA[configPath] = new ConfigMeta<TClass, Dictionary<TKey, TValue>>(configSelection, propertyExpression)
{
Default = defaultValues,
};
return defaultValues;
}
}
@@ -9,7 +9,7 @@ namespace AIStudio.Settings;
public static partial class ManagedConfiguration
{
private static readonly ConcurrentDictionary<string, IConfig> METADATA = new();
private static readonly SettingsManager SETTINGS_MANAGER = Program.SERVICE_PROVIDER.GetRequiredService<SettingsManager>();
private static SettingsManager SettingsManagerAccess => Program.SERVICE_PROVIDER.GetRequiredService<SettingsManager>();
/// <summary>
/// Attempts to retrieve the configuration metadata for a given configuration selection and
@@ -231,6 +231,44 @@ public static partial class ManagedConfiguration
return false;
}
/// <summary>
/// Attempts to retrieve the configuration metadata for an enum dictionary-based setting.
/// </summary>
/// <remarks>
/// When no configuration metadata is found, it returns a NoConfig instance with the default
/// value set to an empty dictionary. This allows the caller to handle the absence of configuration
/// gracefully. In such cases, the return value of the method will be false.
/// </remarks>
/// <param name="configSelection">The expression to select the configuration class.</param>
/// <param name="propertyExpression">The expression to select the property within the
/// configuration class.</param>
/// <param name="configMeta">The output parameter that will hold the configuration metadata
/// if found.</param>
/// <typeparam name="TClass">The type of the configuration class.</typeparam>
/// <typeparam name="TKey">The enum type of the dictionary keys.</typeparam>
/// <typeparam name="TValue">The enum type of the dictionary values.</typeparam>
/// <returns>True if the configuration metadata was found, otherwise false.</returns>
public static bool TryGet<TClass, TKey, TValue>(
Expression<Func<Data, TClass>> configSelection,
Expression<Func<TClass, Dictionary<TKey, TValue>>> propertyExpression,
out ConfigMeta<TClass, Dictionary<TKey, TValue>> configMeta)
where TKey : struct, Enum
where TValue : struct, Enum
{
var configPath = Path(configSelection, propertyExpression);
if (METADATA.TryGetValue(configPath, out var value) && value is ConfigMeta<TClass, Dictionary<TKey, TValue>> meta)
{
configMeta = meta;
return true;
}
configMeta = new NoConfig<TClass, Dictionary<TKey, TValue>>(configSelection, propertyExpression)
{
Default = new Dictionary<TKey, TValue>(),
};
return false;
}
/// <summary>
/// Checks if a configuration setting is left over from a configuration plugin that is no longer available.
/// If the configuration setting is locked and managed by a configuration plugin that is not available,
@@ -399,6 +437,42 @@ public static partial class ManagedConfiguration
return false;
}
public static bool IsConfigurationLeftOver<TClass, TKey, TValue>(
Expression<Func<Data, TClass>> configSelection,
Expression<Func<TClass, Dictionary<TKey, TValue>>> propertyExpression,
IEnumerable<IAvailablePlugin> availablePlugins)
where TKey : struct, Enum
where TValue : struct, Enum
{
if (!TryGet(configSelection, propertyExpression, out var configMeta))
return false;
if (configMeta.ManagedMode is ManagedConfigurationMode.EDITABLE_DEFAULT)
{
var plugin = availablePlugins.FirstOrDefault(x => x.Id == configMeta.EditableDefaultByConfigPluginId);
if (plugin is null)
{
configMeta.ClearEditableDefaultConfiguration();
ClearEditableDefaultState(SettingName(propertyExpression));
return true;
}
return false;
}
if (configMeta.LockedByConfigPluginId == Guid.Empty || !configMeta.IsLocked)
return false;
var lockedPlugin = availablePlugins.FirstOrDefault(x => x.Id == configMeta.LockedByConfigPluginId);
if (lockedPlugin is null)
{
configMeta.ResetLockedConfiguration();
return true;
}
return false;
}
private static string Path<TClass, TValue>(Expression<Func<Data, TClass>> configSelection, Expression<Func<TClass, TValue>> propertyExpression)
{
@@ -418,19 +492,19 @@ public static partial class ManagedConfiguration
private static bool TryGetEditableDefaultState(string settingName, out ManagedEditableDefaultState editableDefaultState)
{
return SETTINGS_MANAGER.ConfigurationData.ManagedEditableDefaults.TryGetValue(settingName, out editableDefaultState!);
return SettingsManagerAccess.ConfigurationData.ManagedEditableDefaults.TryGetValue(settingName, out editableDefaultState!);
}
private static void SetEditableDefaultState(string settingName, Guid pluginId, string lastAppliedValue)
{
SETTINGS_MANAGER.ConfigurationData.ManagedEditableDefaults[settingName] = new()
SettingsManagerAccess.ConfigurationData.ManagedEditableDefaults[settingName] = new()
{
ConfigPluginId = pluginId,
LastAppliedValue = lastAppliedValue,
};
}
private static bool ClearEditableDefaultState(string settingName) => SETTINGS_MANAGER.ConfigurationData.ManagedEditableDefaults.Remove(settingName);
private static bool ClearEditableDefaultState(string settingName) => SettingsManagerAccess.ConfigurationData.ManagedEditableDefaults.Remove(settingName);
private static bool CleanupEditableDefaultState<TClass, TValue>(
ConfigMeta<TClass, TValue> configMeta,
+2 -2
View File
@@ -73,7 +73,7 @@ public sealed record Provider(
/// <inheritdoc />
[JsonIgnore]
public string SecretId => this.IsEnterpriseConfiguration ? $"{ISecretId.ENTERPRISE_KEY_PREFIX}::{this.UsedLLMProvider.ToName()}" : this.UsedLLMProvider.ToName();
public string SecretId => this.IsEnterpriseConfiguration ? $"{ISecretId.ENTERPRISE_KEY_PREFIX}::{this.UsedLLMProvider.ToSecretId()}" : this.UsedLLMProvider.ToSecretId();
/// <inheritdoc />
[JsonIgnore]
@@ -191,7 +191,7 @@ public sealed record Provider(
{
// Queue the API key for storage in the OS keyring:
PendingEnterpriseApiKeys.Add(new(
$"{ISecretId.ENTERPRISE_KEY_PREFIX}::{usedLLMProvider.ToName()}",
$"{ISecretId.ENTERPRISE_KEY_PREFIX}::{usedLLMProvider.ToSecretId()}",
instanceName,
decryptedApiKey,
SecretStoreType.LLM_PROVIDER));
@@ -17,6 +17,11 @@ namespace AIStudio.Settings;
public sealed class SettingsManager
{
private const string SETTINGS_FILENAME = "settings.json";
private const Version CURRENT_SETTINGS_VERSION = Version.V6;
private readonly record struct SettingsVersionReadResult(Version Version, SettingsWriteBlockReason FailureReason);
private readonly record struct CurrentSettingsReadResult(Data? SettingsData, SettingsWriteBlockReason FailureReason);
private static readonly JsonSerializerOptions JSON_OPTIONS = new()
{
@@ -62,6 +67,16 @@ public sealed class SettingsManager
/// </summary>
public bool HasCompletedInitialSettingsLoad { get; private set; }
/// <summary>
/// Indicates why settings writes are blocked for the current session.
/// </summary>
public SettingsWriteBlockReason SettingsWriteBlockReason { get; private set; } = SettingsWriteBlockReason.NONE;
/// <summary>
/// Indicates that settings writes are blocked for the current session.
/// </summary>
public bool SettingsWriteBlocked => this.SettingsWriteBlockReason is not SettingsWriteBlockReason.NONE;
/// <summary>
/// The configuration data.
/// </summary>
@@ -87,6 +102,7 @@ public sealed class SettingsManager
/// <returns>A (migrated) settings snapshot, or null if it could not be read.</returns>
public async Task<Data?> TryReadSettingsSnapshot()
{
this.SettingsWriteBlockReason = SettingsWriteBlockReason.NONE;
if(!this.IsSetUp)
{
this.logger.LogWarning("Cannot load settings, because the configuration is not set up yet.");
@@ -100,38 +116,175 @@ public sealed class SettingsManager
return null;
}
// We read the `"Version": "V3"` line to determine the version of the settings file:
await foreach (var line in File.ReadLinesAsync(settingsPath))
var settingsVersion = await this.TryReadSettingsVersion(settingsPath);
if(settingsVersion.FailureReason is not SettingsWriteBlockReason.NONE)
{
if (!line.Contains("""
"Version":
"""))
continue;
this.BlockSettingsWrites(settingsVersion.FailureReason, "The settings file version could not be identified. Settings writes are blocked to avoid overwriting newer or unreadable settings.");
return await this.TryReadCurrentVersionBackupSnapshotForBlockedSettings();
}
// Extract the version from the line:
var settingsVersionText = line.Split('"')[3];
if(settingsVersion.Version > CURRENT_SETTINGS_VERSION)
{
this.BlockSettingsWrites(SettingsWriteBlockReason.VERSION_NEWER_THAN_APP, $"The settings file uses the newer version '{settingsVersion.Version}'. Settings writes are blocked to avoid overwriting newer settings.");
return await this.TryReadCurrentVersionBackupSnapshotForBlockedSettings();
}
// Parse the version:
Enum.TryParse(settingsVersionText, out Version settingsVersion);
if(settingsVersion is Version.UNKNOWN)
Data? settingsData;
if(settingsVersion.Version < CURRENT_SETTINGS_VERSION)
{
settingsData = await this.TryReadCurrentVersionBackupSnapshot();
if(settingsData is not null)
{
this.logger.LogError("Unknown version of the settings file found.");
return new();
this.PrepareLoadedSettings(settingsData);
await this.StoreSettingsSnapshot(settingsData, settingsPath);
await this.StoreCurrentVersionBackup(settingsData);
this.logger.LogInformation($"Restored settings from the '{GetBackupSettingsFilename(CURRENT_SETTINGS_VERSION)}' backup file.");
return settingsData;
}
var settingsData = SettingsMigrations.Migrate(this.logger, settingsVersion, await File.ReadAllTextAsync(settingsPath), JSON_OPTIONS);
//
// We filter the enabled preview features based on the preview visibility.
// This is necessary when the app starts up: some preview features may have
// been disabled or released from the last time the app was started.
//
settingsData.App.EnabledPreviewFeatures = settingsData.App.PreviewVisibility.FilterPreviewFeatures(settingsData.App.EnabledPreviewFeatures);
this.logger.LogInformation("No valid current-version settings backup was found. Migrating the settings file.");
settingsData = SettingsMigrations.Migrate(this.logger, settingsVersion.Version, await File.ReadAllTextAsync(settingsPath), JSON_OPTIONS);
this.PrepareLoadedSettings(settingsData);
await this.StoreSettingsSnapshot(settingsData, settingsPath);
await this.StoreCurrentVersionBackup(settingsData);
return settingsData;
}
this.logger.LogError("Failed to read the version of the settings file.");
return new();
var currentSettings = await this.TryDeserializeCurrentSettings(settingsPath, "settings file");
if(currentSettings.FailureReason is not SettingsWriteBlockReason.NONE)
{
this.BlockSettingsWrites(currentSettings.FailureReason, "The current settings file could not be safely loaded. Settings writes are blocked to avoid overwriting recoverable settings.");
return await this.TryReadCurrentVersionBackupSnapshotForBlockedSettings();
}
settingsData = currentSettings.SettingsData!;
this.PrepareLoadedSettings(settingsData);
await this.StoreCurrentVersionBackup(settingsData);
return settingsData;
}
private async Task<SettingsVersionReadResult> TryReadSettingsVersion(string settingsPath)
{
try
{
await using var settingsStream = File.OpenRead(settingsPath);
using var settingsDocument = await JsonDocument.ParseAsync(settingsStream);
if(!settingsDocument.RootElement.TryGetProperty("Version", out var versionElement))
{
this.logger.LogError($"Failed to read the version of the settings file '{settingsPath}'.");
return new(Version.UNKNOWN, SettingsWriteBlockReason.VERSION_MISSING);
}
if(versionElement.ValueKind is JsonValueKind.String && versionElement.GetString() is { } versionText)
{
if(Enum.TryParse(versionText, out Version stringVersion) && Enum.IsDefined(stringVersion) && stringVersion is not Version.UNKNOWN)
return new(stringVersion, SettingsWriteBlockReason.NONE);
if(versionText.StartsWith('V') && int.TryParse(versionText[1..], out var futureVersion) && futureVersion > (int)CURRENT_SETTINGS_VERSION)
return new((Version)futureVersion, SettingsWriteBlockReason.NONE);
if(int.TryParse(versionText, out var numericStringVersion) && numericStringVersion > (int)CURRENT_SETTINGS_VERSION)
return new((Version)numericStringVersion, SettingsWriteBlockReason.NONE);
}
if(versionElement.ValueKind is JsonValueKind.Number && versionElement.TryGetInt32(out var numericVersion) && numericVersion > (int)Version.UNKNOWN && (Enum.IsDefined(typeof(Version), numericVersion) || numericVersion > (int)CURRENT_SETTINGS_VERSION))
return new((Version)numericVersion, SettingsWriteBlockReason.NONE);
}
catch(Exception e)
{
this.logger.LogError(e, $"Failed to read the version of the settings file '{settingsPath}'.");
return new(Version.UNKNOWN, SettingsWriteBlockReason.FILE_UNREADABLE);
}
return new(Version.UNKNOWN, SettingsWriteBlockReason.VERSION_UNKNOWN);
}
private async Task<Data?> TryReadCurrentVersionBackupSnapshot()
{
var backupSettingsPath = GetBackupSettingsPath(CURRENT_SETTINGS_VERSION);
if(!File.Exists(backupSettingsPath))
{
this.logger.LogInformation($"The settings backup file '{backupSettingsPath}' does not exist.");
return null;
}
var backupVersion = await this.TryReadSettingsVersion(backupSettingsPath);
if(backupVersion.FailureReason is not SettingsWriteBlockReason.NONE)
{
this.logger.LogWarning($"The settings backup file '{backupSettingsPath}' could not be used because its version could not be identified. Reason: '{backupVersion.FailureReason}'.");
return null;
}
if(backupVersion.Version != CURRENT_SETTINGS_VERSION)
{
this.logger.LogWarning($"The settings backup file '{backupSettingsPath}' uses version '{backupVersion.Version}' instead of '{CURRENT_SETTINGS_VERSION}'.");
return null;
}
var backupSettings = await this.TryDeserializeCurrentSettings(backupSettingsPath, "settings backup file");
if(backupSettings.FailureReason is not SettingsWriteBlockReason.NONE)
{
this.logger.LogWarning($"The settings backup file '{backupSettingsPath}' could not be used. Reason: '{backupSettings.FailureReason}'.");
return null;
}
return backupSettings.SettingsData;
}
private async Task<Data?> TryReadCurrentVersionBackupSnapshotForBlockedSettings()
{
var settingsData = await this.TryReadCurrentVersionBackupSnapshot();
if(settingsData is null)
{
this.logger.LogWarning($"No valid current-version settings backup was found while settings writes are blocked. Reason: '{this.SettingsWriteBlockReason}'.");
return null;
}
this.PrepareLoadedSettings(settingsData);
this.logger.LogWarning($"Loaded settings from the '{GetBackupSettingsFilename(CURRENT_SETTINGS_VERSION)}' backup file while settings writes remain blocked. Reason: '{this.SettingsWriteBlockReason}'.");
return settingsData;
}
private async Task<CurrentSettingsReadResult> TryDeserializeCurrentSettings(string settingsPath, string sourceDescription)
{
try
{
var settingsData = JsonSerializer.Deserialize<Data>(await File.ReadAllTextAsync(settingsPath), JSON_OPTIONS);
if(settingsData is null)
{
this.logger.LogError($"Failed to parse the {sourceDescription} '{settingsPath}'.");
return new(null, SettingsWriteBlockReason.CURRENT_VERSION_INVALID);
}
if(settingsData.Version != CURRENT_SETTINGS_VERSION)
{
this.logger.LogError($"The {sourceDescription} '{settingsPath}' uses version '{settingsData.Version}' instead of '{CURRENT_SETTINGS_VERSION}'.");
return new(null, SettingsWriteBlockReason.CURRENT_VERSION_INVALID);
}
return new(settingsData, SettingsWriteBlockReason.NONE);
}
catch(Exception e)
{
this.logger.LogError(e, $"Failed to parse the {sourceDescription} '{settingsPath}'.");
return new(null, SettingsWriteBlockReason.FILE_UNREADABLE);
}
}
private void BlockSettingsWrites(SettingsWriteBlockReason reason, string message)
{
this.SettingsWriteBlockReason = reason;
this.logger.LogError($"{message} Reason: '{reason}'.");
}
private void PrepareLoadedSettings(Data settingsData)
{
//
// We filter the enabled preview features based on the preview visibility.
// This is necessary when the app starts up: some preview features may have
// been disabled or released from the last time the app was started.
//
settingsData.App.EnabledPreviewFeatures = settingsData.App.PreviewVisibility.FilterPreviewFeatures(settingsData.App.EnabledPreviewFeatures);
}
/// <summary>
@@ -145,19 +298,48 @@ public sealed class SettingsManager
return;
}
if(this.SettingsWriteBlocked)
{
this.logger.LogWarning($"Cannot store settings, because settings writes are blocked. Reason: '{this.SettingsWriteBlockReason}'.");
return;
}
var settingsPath = Path.Combine(ConfigDirectory!, SETTINGS_FILENAME);
await this.StoreSettingsSnapshot(this.ConfigurationData, settingsPath);
await this.StoreCurrentVersionBackup(this.ConfigurationData);
}
private static string GetBackupSettingsFilename(Version version) => $"settings.{version.ToString().ToLowerInvariant()}.json";
private static string GetBackupSettingsPath(Version version) => Path.Combine(ConfigDirectory!, GetBackupSettingsFilename(version));
private async Task StoreCurrentVersionBackup(Data settingsData)
{
if(settingsData.Version != CURRENT_SETTINGS_VERSION)
{
this.logger.LogWarning($"Skipping settings backup because the settings version '{settingsData.Version}' is not the current version '{CURRENT_SETTINGS_VERSION}'.");
return;
}
var backupSettingsPath = GetBackupSettingsPath(CURRENT_SETTINGS_VERSION);
await this.StoreSettingsSnapshot(settingsData, backupSettingsPath);
this.logger.LogInformation($"Stored the settings backup file '{backupSettingsPath}'.");
}
private async Task StoreSettingsSnapshot(Data settingsData, string settingsPath)
{
if(!Directory.Exists(ConfigDirectory))
{
this.logger.LogInformation("Creating the configuration directory.");
Directory.CreateDirectory(ConfigDirectory!);
}
var settingsJson = JsonSerializer.Serialize(this.ConfigurationData, JSON_OPTIONS);
var settingsJson = JsonSerializer.Serialize(settingsData, JSON_OPTIONS);
var tempFile = Path.GetTempFileName();
await File.WriteAllTextAsync(tempFile, settingsJson);
File.Move(tempFile, settingsPath, true);
this.logger.LogInformation("Stored the settings to the file system.");
this.logger.LogInformation($"Stored the settings to '{settingsPath}'.");
}
public void InjectSpellchecking(Dictionary<string, object?> attributes) => attributes["spellcheck"] = this.ConfigurationData.App.EnableSpellchecking ? "true" : "false";
@@ -165,9 +347,9 @@ public sealed class SettingsManager
public ConfidenceLevel GetMinimumConfidenceLevel(Tools.Components component)
{
var minimumLevel = ConfidenceLevel.NONE;
var enforceGlobalMinimumConfidence = this.ConfigurationData.LLMProviders is { EnforceGlobalMinimumConfidence: true, GlobalMinimumConfidence: not ConfidenceLevel.NONE and not ConfidenceLevel.UNKNOWN };
var enforceGlobalMinimumConfidence = this.ConfigurationData.Confidence is { EnforceGlobalMinimumConfidence: true, GlobalMinimumConfidence: not ConfidenceLevel.NONE and not ConfidenceLevel.UNKNOWN };
if (enforceGlobalMinimumConfidence)
minimumLevel = this.ConfigurationData.LLMProviders.GlobalMinimumConfidence;
minimumLevel = this.ConfigurationData.Confidence.GlobalMinimumConfidence;
var componentMinimumLevel = component.MinimumConfidence(this);
if (componentMinimumLevel > minimumLevel)
@@ -348,17 +530,13 @@ public sealed class SettingsManager
return Profile.NO_PROFILE;
if (preselection.UseSpecificProfile)
{
var componentProfile = this.ConfigurationData.Profiles.FirstOrDefault(x => x.Id.Equals(preselection.SpecificProfileId, StringComparison.OrdinalIgnoreCase));
return componentProfile ?? Profile.NO_PROFILE;
}
return this.GetProfileById(preselection.SpecificProfileId);
var appPreselection = ProfilePreselection.FromStoredValue(this.ConfigurationData.App.PreselectedProfile);
if (appPreselection.DoNotPreselectProfile || !appPreselection.UseSpecificProfile)
return Profile.NO_PROFILE;
var appProfile = this.ConfigurationData.Profiles.FirstOrDefault(x => x.Id.Equals(appPreselection.SpecificProfileId, StringComparison.OrdinalIgnoreCase));
return appProfile ?? Profile.NO_PROFILE;
return this.GetProfileById(appPreselection.SpecificProfileId);
}
public Profile GetAppPreselectedProfile()
@@ -367,8 +545,7 @@ public sealed class SettingsManager
if (appPreselection.DoNotPreselectProfile || !appPreselection.UseSpecificProfile)
return Profile.NO_PROFILE;
var appProfile = this.ConfigurationData.Profiles.FirstOrDefault(x => x.Id.Equals(appPreselection.SpecificProfileId, StringComparison.OrdinalIgnoreCase));
return appProfile ?? Profile.NO_PROFILE;
return this.GetProfileById(appPreselection.SpecificProfileId);
}
public ChatTemplate GetPreselectedChatTemplate(Tools.Components component)
@@ -377,8 +554,29 @@ public sealed class SettingsManager
if (preselection != ChatTemplate.NO_CHAT_TEMPLATE)
return preselection;
preselection = this.ConfigurationData.ChatTemplates.FirstOrDefault(x => x.Id.Equals(this.ConfigurationData.App.PreselectedChatTemplate, StringComparison.OrdinalIgnoreCase));
return preselection ?? ChatTemplate.NO_CHAT_TEMPLATE;
return this.GetChatTemplateById(this.ConfigurationData.App.PreselectedChatTemplate);
}
public Profile GetProfileById(string? profileId)
{
if (string.IsNullOrWhiteSpace(profileId))
return Profile.NO_PROFILE;
if (string.Equals(profileId, Profile.NO_PROFILE.Id, StringComparison.OrdinalIgnoreCase))
return Profile.NO_PROFILE;
return this.ConfigurationData.Profiles.FirstOrDefault(x => x.Id.Equals(profileId, StringComparison.OrdinalIgnoreCase)) ?? Profile.NO_PROFILE;
}
public ChatTemplate GetChatTemplateById(string? chatTemplateId)
{
if (string.IsNullOrWhiteSpace(chatTemplateId))
return ChatTemplate.NO_CHAT_TEMPLATE;
if (string.Equals(chatTemplateId, ChatTemplate.NO_CHAT_TEMPLATE.Id, StringComparison.OrdinalIgnoreCase))
return ChatTemplate.NO_CHAT_TEMPLATE;
return this.ConfigurationData.ChatTemplates.FirstOrDefault(x => x.Id.Equals(chatTemplateId, StringComparison.OrdinalIgnoreCase)) ?? ChatTemplate.NO_CHAT_TEMPLATE;
}
public ConfidenceLevel GetConfiguredConfidenceLevel(LLMProviders llmProvider)
@@ -386,7 +584,7 @@ public sealed class SettingsManager
if(llmProvider is LLMProviders.NONE)
return ConfidenceLevel.NONE;
switch (this.ConfigurationData.LLMProviders.ConfidenceScheme)
switch (this.ConfigurationData.Confidence.ConfidenceScheme)
{
case ConfidenceSchemes.TRUST_ALL:
return llmProvider switch
@@ -446,7 +644,7 @@ public sealed class SettingsManager
};
case ConfidenceSchemes.CUSTOM:
return this.ConfigurationData.LLMProviders.CustomConfidenceScheme.GetValueOrDefault(llmProvider, ConfidenceLevel.UNKNOWN);
return this.ConfigurationData.Confidence.CustomConfidenceScheme.GetValueOrDefault(llmProvider, ConfidenceLevel.UNKNOWN);
default:
return ConfidenceLevel.UNKNOWN;
@@ -469,4 +667,4 @@ public sealed class SettingsManager
// Return the full name of the property, including the class name:
return $"{typeof(TIn).Name}.{memberExpr.Member.Name}";
}
}
}
@@ -24,7 +24,8 @@ public static class SettingsMigrations
configV1 = MigrateV1ToV2(logger, configV1);
configV1 = MigrateV2ToV3(logger, configV1);
var configV14 = MigrateV3ToV4(logger, configV1);
return MigrateV4ToV5(logger, configV14);
var configV15 = MigrateV4ToV5(logger, configV14);
return MigrateV5ToV6(logger, configV15);
case Version.V2:
var configV2 = JsonSerializer.Deserialize<DataV1V3>(configData, jsonOptions);
@@ -36,7 +37,8 @@ public static class SettingsMigrations
configV2 = MigrateV2ToV3(logger, configV2);
var configV24 = MigrateV3ToV4(logger, configV2);
return MigrateV4ToV5(logger, configV24);
var configV25 = MigrateV4ToV5(logger, configV24);
return MigrateV5ToV6(logger, configV25);
case Version.V3:
var configV3 = JsonSerializer.Deserialize<DataV1V3>(configData, jsonOptions);
@@ -47,8 +49,9 @@ public static class SettingsMigrations
}
var configV34 = MigrateV3ToV4(logger, configV3);
return MigrateV4ToV5(logger, configV34);
var configV35 = MigrateV4ToV5(logger, configV34);
return MigrateV5ToV6(logger, configV35);
case Version.V4:
var configV4 = JsonSerializer.Deserialize<DataV4>(configData, jsonOptions);
if (configV4 is null)
@@ -57,18 +60,29 @@ public static class SettingsMigrations
return new();
}
return MigrateV4ToV5(logger, configV4);
var configV45 = MigrateV4ToV5(logger, configV4);
return MigrateV5ToV6(logger, configV45);
default:
logger.LogInformation("No configuration migration is needed.");
var configV5 = JsonSerializer.Deserialize<Data>(configData, jsonOptions);
case Version.V5:
var configV5 = JsonSerializer.Deserialize<DataV5>(configData, jsonOptions);
if (configV5 is null)
{
logger.LogError("Failed to parse the v4 configuration. Using default values.");
logger.LogError("Failed to parse the v5 configuration. Using default values.");
return new();
}
return configV5;
return MigrateV5ToV6(logger, configV5);
default:
logger.LogInformation("No configuration migration is needed.");
var configV6 = JsonSerializer.Deserialize<Data>(configData, jsonOptions);
if (configV6 is null)
{
logger.LogError("Failed to parse the v6 configuration. Using default values.");
return new();
}
return configV6;
}
}
@@ -83,9 +97,9 @@ public static class SettingsMigrations
return new()
{
Version = Version.V2,
Providers = previousData.Providers.Select(provider => provider with { IsSelfHosted = false, Hostname = string.Empty }).ToList(),
EnableSpellchecking = previousData.EnableSpellchecking,
IsSavingEnergy = previousData.IsSavingEnergy,
NextProviderNum = previousData.NextProviderNum,
@@ -93,7 +107,7 @@ public static class SettingsMigrations
UpdateInterval = previousData.UpdateInterval,
};
}
private static DataV1V3 MigrateV2ToV3(ILogger<SettingsManager> logger, DataV1V3 previousData)
{
//
@@ -109,7 +123,7 @@ public static class SettingsMigrations
{
if(provider.IsSelfHosted)
return provider with { Host = Host.LM_STUDIO };
return provider with { Host = Host.NONE };
}).ToList(),
@@ -129,14 +143,14 @@ public static class SettingsMigrations
// Summary:
// We grouped the settings into different categories.
//
logger.LogInformation("Migrating from v3 to v4...");
return new()
{
Version = Version.V4,
Providers = previousConfig.Providers,
NextProviderNum = previousConfig.NextProviderNum,
App = new(x => x.App)
{
EnableSpellchecking = previousConfig.EnableSpellchecking,
@@ -144,27 +158,27 @@ public static class SettingsMigrations
UpdateInterval = previousConfig.UpdateInterval,
NavigationBehavior = previousConfig.NavigationBehavior,
},
Chat = new()
{
ShortcutSendBehavior = previousConfig.ShortcutSendBehavior,
PreselectOptions = previousConfig.PreselectChatOptions,
PreselectedProvider = previousConfig.PreselectedChatProvider,
},
Workspace = new()
{
StorageBehavior = previousConfig.WorkspaceStorageBehavior,
StorageTemporaryMaintenancePolicy = previousConfig.WorkspaceStorageTemporaryMaintenancePolicy,
},
IconFinder = new()
{
PreselectOptions = previousConfig.PreselectIconOptions,
PreselectedProvider = previousConfig.PreselectedIconProvider,
PreselectedSource = previousConfig.PreselectedIconSource,
},
Translation = new()
{
PreselectLiveTranslation = previousConfig.PreselectLiveTranslation,
@@ -177,7 +191,7 @@ public static class SettingsMigrations
PreselectContentCleanerAgent = previousConfig.PreselectContentCleanerAgentForTranslation,
PreselectWebContentReader = previousConfig.PreselectWebContentReaderForTranslation,
},
Coding = new()
{
PreselectOptions = previousConfig.PreselectCodingOptions,
@@ -186,7 +200,7 @@ public static class SettingsMigrations
PreselectedOtherProgrammingLanguage = previousConfig.PreselectedCodingOtherLanguage,
PreselectCompilerMessages = previousConfig.PreselectCodingCompilerMessages,
},
TextSummarizer = new()
{
PreselectOptions = previousConfig.PreselectTextSummarizerOptions,
@@ -199,7 +213,7 @@ public static class SettingsMigrations
PreselectContentCleanerAgent = previousConfig.PreselectContentCleanerAgentForTextSummarizer,
PreselectWebContentReader = previousConfig.PreselectWebContentReaderForTextSummarizer,
},
TextContentCleaner = new()
{
PreselectAgentOptions = previousConfig.PreselectAgentTextContentCleanerOptions,
@@ -207,14 +221,14 @@ public static class SettingsMigrations
},
};
}
private static Data MigrateV4ToV5(ILogger<SettingsManager> logger, DataV4 previousConfig)
private static DataV5 MigrateV4ToV5(ILogger<SettingsManager> logger, DataV4 previousConfig)
{
//
// Summary:
// We renamed the LLM provider enum.
//
logger.LogInformation("Migrating from v4 to v5...");
return new()
{
@@ -241,4 +255,68 @@ public static class SettingsMigrations
MyTasks = previousConfig.MyTasks,
};
}
private static Data MigrateV5ToV6(ILogger<SettingsManager> logger, DataV5 previousConfig)
{
//
// Summary:
// We moved confidence settings out of LLM provider settings.
//
logger.LogInformation("Migrating from v5 to v6...");
return new()
{
Version = Version.V6,
Providers = previousConfig.Providers,
Confidence = new(x => x.Confidence)
{
EnforceGlobalMinimumConfidence = previousConfig.LLMProviders.EnforceGlobalMinimumConfidence,
GlobalMinimumConfidence = previousConfig.LLMProviders.GlobalMinimumConfidence,
ShowProviderConfidence = previousConfig.LLMProviders.ShowProviderConfidence,
ConfidenceScheme = previousConfig.LLMProviders.ConfidenceScheme,
CustomConfidenceScheme = previousConfig.LLMProviders.CustomConfidenceScheme,
},
EmbeddingProviders = previousConfig.EmbeddingProviders,
TranscriptionProviders = previousConfig.TranscriptionProviders,
DataSources = previousConfig.DataSources,
Profiles = previousConfig.Profiles,
ChatTemplates = previousConfig.ChatTemplates,
EnabledPlugins = previousConfig.EnabledPlugins,
ManagedEditableDefaults = previousConfig.ManagedEditableDefaults,
AssistantPluginAudits = previousConfig.AssistantPluginAudits,
NextProviderNum = previousConfig.NextProviderNum,
NextEmbeddingNum = previousConfig.NextEmbeddingNum,
NextTranscriptionNum = previousConfig.NextTranscriptionNum,
NextDataSourceNum = previousConfig.NextDataSourceNum,
NextProfileNum = previousConfig.NextProfileNum,
NextChatTemplateNum = previousConfig.NextChatTemplateNum,
NextDocumentAnalysisPolicyNum = previousConfig.NextDocumentAnalysisPolicyNum,
App = previousConfig.App,
Chat = previousConfig.Chat,
Workspace = previousConfig.Workspace,
IconFinder = previousConfig.IconFinder,
Translation = previousConfig.Translation,
Coding = previousConfig.Coding,
ERI = previousConfig.ERI,
DocumentAnalysis = previousConfig.DocumentAnalysis,
MandatoryInformation = previousConfig.MandatoryInformation,
TextSummarizer = previousConfig.TextSummarizer,
TextContentCleaner = previousConfig.TextContentCleaner,
AgentDataSourceSelection = previousConfig.AgentDataSourceSelection,
AgentRetrievalContextValidation = previousConfig.AgentRetrievalContextValidation,
AssistantPluginAudit = previousConfig.AssistantPluginAudit,
Agenda = previousConfig.Agenda,
GrammarSpelling = previousConfig.GrammarSpelling,
RewriteImprove = previousConfig.RewriteImprove,
PromptOptimizer = previousConfig.PromptOptimizer,
EMail = previousConfig.EMail,
SlideBuilder = previousConfig.SlideBuilder,
LegalCheck = previousConfig.LegalCheck,
Synonyms = previousConfig.Synonyms,
MyTasks = previousConfig.MyTasks,
JobPostings = previousConfig.JobPostings,
BiasOfTheDay = previousConfig.BiasOfTheDay,
I18N = previousConfig.I18N,
};
}
}
@@ -0,0 +1,11 @@
namespace AIStudio.Settings;
public enum SettingsWriteBlockReason
{
NONE,
VERSION_MISSING,
VERSION_UNKNOWN,
VERSION_NEWER_THAN_APP,
FILE_UNREADABLE,
CURRENT_VERSION_INVALID,
}
@@ -44,7 +44,7 @@ public sealed record TranscriptionProvider(
/// <inheritdoc />
[JsonIgnore]
public string SecretId => this.IsEnterpriseConfiguration ? $"{ISecretId.ENTERPRISE_KEY_PREFIX}::{this.UsedLLMProvider.ToName()}" : this.UsedLLMProvider.ToName();
public string SecretId => this.IsEnterpriseConfiguration ? $"{ISecretId.ENTERPRISE_KEY_PREFIX}::{this.UsedLLMProvider.ToSecretId()}" : this.UsedLLMProvider.ToSecretId();
/// <inheritdoc />
[JsonIgnore]
@@ -125,7 +125,7 @@ public sealed record TranscriptionProvider(
{
// Queue the API key for storage in the OS keyring:
PendingEnterpriseApiKeys.Add(new(
$"{ISecretId.ENTERPRISE_KEY_PREFIX}::{usedLLMProvider.ToName()}",
$"{ISecretId.ENTERPRISE_KEY_PREFIX}::{usedLLMProvider.ToSecretId()}",
name,
decryptedApiKey,
SecretStoreType.TRANSCRIPTION_PROVIDER));
@@ -13,4 +13,5 @@ public enum Version
V3,
V4,
V5,
V6,
}