diff --git a/app/MindWork AI Studio/Chat/ChatThread.cs b/app/MindWork AI Studio/Chat/ChatThread.cs index e8277cb5..2c9bb720 100644 --- a/app/MindWork AI Studio/Chat/ChatThread.cs +++ b/app/MindWork AI Studio/Chat/ChatThread.cs @@ -94,6 +94,8 @@ public sealed record ChatThread /// The prepared system prompt. public string PrepareSystemPrompt(SettingsManager settingsManager) { + this.allowProfile = true; + // // Use the information from the chat template, if provided. Otherwise, use the default system prompt // @@ -111,8 +113,8 @@ public sealed record ChatThread systemPromptTextWithChatTemplate = this.SystemPrompt; else { - var chatTemplate = settingsManager.ConfigurationData.ChatTemplates.FirstOrDefault(x => x.Id == this.SelectedChatTemplate); - if(chatTemplate == null) + var chatTemplate = settingsManager.GetChatTemplateById(this.SelectedChatTemplate); + if(chatTemplate == ChatTemplate.NO_CHAT_TEMPLATE) systemPromptTextWithChatTemplate = this.SystemPrompt; else { @@ -168,8 +170,8 @@ public sealed record ChatThread systemPromptText = systemPromptWithAugmentedData; else { - var profile = settingsManager.ConfigurationData.Profiles.FirstOrDefault(x => x.Id == this.SelectedProfile); - if(profile is null) + var profile = settingsManager.GetProfileById(this.SelectedProfile); + if(profile == Profile.NO_PROFILE) systemPromptText = systemPromptWithAugmentedData; else { diff --git a/app/MindWork AI Studio/Components/ChatComponent.razor.cs b/app/MindWork AI Studio/Components/ChatComponent.razor.cs index 62caf008..aa2b099d 100644 --- a/app/MindWork AI Studio/Components/ChatComponent.razor.cs +++ b/app/MindWork AI Studio/Components/ChatComponent.razor.cs @@ -101,7 +101,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable protected override async Task OnInitializedAsync() { // Apply the filters for the message bus: - this.ApplyFilters([], [ Event.HAS_CHAT_UNSAVED_CHANGES, Event.RESET_CHAT_STATE, Event.CHAT_STREAMING_DONE, Event.AI_JOB_CHANGED, Event.AI_JOB_FINISHED, Event.CHAT_GENERATION_CHANGED, Event.WORKSPACE_RENAMED ]); + this.ApplyFilters([], [ Event.HAS_CHAT_UNSAVED_CHANGES, Event.RESET_CHAT_STATE, Event.CHAT_STREAMING_DONE, Event.AI_JOB_CHANGED, Event.AI_JOB_FINISHED, Event.CHAT_GENERATION_CHANGED, Event.WORKSPACE_RENAMED, Event.CONFIGURATION_CHANGED ]); // Configure the spellchecking for the user input: this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES); @@ -470,7 +470,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable private async Task ProfileWasChanged(Profile profile) { - this.currentProfile = profile; + this.currentProfile = this.SettingsManager.GetProfileById(profile.Id); if(this.ChatThread is null) return; @@ -484,7 +484,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable private async Task ChatTemplateWasChanged(ChatTemplate chatTemplate) { - this.currentChatTemplate = chatTemplate; + this.currentChatTemplate = this.SettingsManager.GetChatTemplateById(chatTemplate.Id); if(!string.IsNullOrWhiteSpace(this.currentChatTemplate.PredefinedUserPrompt)) this.ComposerState.SetSystemInput(this.currentChatTemplate.PredefinedUserPrompt); @@ -497,6 +497,12 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable await this.StartNewChat(true); } + private void RefreshCurrentProfileAndChatTemplate() + { + this.currentProfile = this.SettingsManager.GetProfileById(this.currentProfile.Id); + this.currentChatTemplate = this.SettingsManager.GetChatTemplateById(this.currentChatTemplate.Id); + } + private IReadOnlyList GetAgentSelectedDataSources() { if (this.ChatThread is null) @@ -601,7 +607,9 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable if(!this.ChatThread.IsLLMProviderAllowed(this.Provider)) return; - + + this.RefreshCurrentProfileAndChatTemplate(); + // Blur the focus away from the input field to be able to clear it: await this.inputField.BlurAsync(); @@ -795,6 +803,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable // this.hasUnsavedChanges = false; this.ComposerState.Clear(); + this.RefreshCurrentProfileAndChatTemplate(); // // Reset the LLM provider considering the user's settings: @@ -967,14 +976,11 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable // Try to select the profile: if (!string.IsNullOrWhiteSpace(chatProfile)) - this.currentProfile = this.SettingsManager.ConfigurationData.Profiles.FirstOrDefault(x => x.Id == chatProfile) ?? Profile.NO_PROFILE; + this.currentProfile = this.SettingsManager.GetProfileById(chatProfile); // Try to select the chat template: if (!string.IsNullOrWhiteSpace(chatChatTemplate)) - { - var selectedTemplate = this.SettingsManager.ConfigurationData.ChatTemplates.FirstOrDefault(x => x.Id == chatChatTemplate); - this.currentChatTemplate = selectedTemplate ?? ChatTemplate.NO_CHAT_TEMPLATE; - } + this.currentChatTemplate = this.SettingsManager.GetChatTemplateById(chatChatTemplate); } private async Task ToggleWorkspaceOverlay() @@ -1074,6 +1080,15 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable if (data is Guid workspaceId) await this.RefreshRenamedWorkspaceHeaderAsync(workspaceId); break; + + case Event.CONFIGURATION_CHANGED: + var previousChatTemplate = this.currentChatTemplate; + this.RefreshCurrentProfileAndChatTemplate(); + if (!this.ComposerState.HasUserDraft && previousChatTemplate != this.currentChatTemplate) + this.ComposerState.ApplyTemplate(this.currentChatTemplate); + + this.StateHasChanged(); + break; case Event.AI_JOB_CHANGED: case Event.AI_JOB_FINISHED: diff --git a/app/MindWork AI Studio/Provider/AlibabaCloud/ProviderAlibabaCloud.cs b/app/MindWork AI Studio/Provider/AlibabaCloud/ProviderAlibabaCloud.cs index 79aef2bc..d2ecc050 100644 --- a/app/MindWork AI Studio/Provider/AlibabaCloud/ProviderAlibabaCloud.cs +++ b/app/MindWork AI Studio/Provider/AlibabaCloud/ProviderAlibabaCloud.cs @@ -68,7 +68,7 @@ public sealed class ProviderAlibabaCloud() : BaseProvider(LLMProviders.ALIBABA_C /// public override async Task>> EmbedTextAsync(Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) { - var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.EMBEDDING_PROVIDER); + var requestedSecret = await Program.RUST_SERVICE.GetAPIKey(this, SecretStoreType.EMBEDDING_PROVIDER); return await this.PerformStandardTextEmbeddingRequest(requestedSecret, embeddingModel, token: token, texts: texts); } diff --git a/app/MindWork AI Studio/Provider/Anthropic/ProviderAnthropic.cs b/app/MindWork AI Studio/Provider/Anthropic/ProviderAnthropic.cs index 1f322788..8e479ffb 100644 --- a/app/MindWork AI Studio/Provider/Anthropic/ProviderAnthropic.cs +++ b/app/MindWork AI Studio/Provider/Anthropic/ProviderAnthropic.cs @@ -27,7 +27,7 @@ public sealed class ProviderAnthropic() : BaseProvider(LLMProviders.ANTHROPIC, n public override async IAsyncEnumerable StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default) { // Get the API key: - var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER); + var requestedSecret = await Program.RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER); if(!requestedSecret.Success) yield break; @@ -93,7 +93,7 @@ public sealed class ProviderAnthropic() : BaseProvider(LLMProviders.ANTHROPIC, n var request = new HttpRequestMessage(HttpMethod.Post, "messages"); // Set the authorization header: - request.Headers.Add("x-api-key", await requestedSecret.Secret.Decrypt(ENCRYPTION)); + request.Headers.Add("x-api-key", await requestedSecret.Secret.Decrypt(Program.ENCRYPTION)); // Set the Anthropic version: request.Headers.Add("anthropic-version", "2023-06-01"); diff --git a/app/MindWork AI Studio/Provider/BaseProvider.cs b/app/MindWork AI Studio/Provider/BaseProvider.cs index e4ce964d..68ecbfae 100644 --- a/app/MindWork AI Studio/Provider/BaseProvider.cs +++ b/app/MindWork AI Studio/Provider/BaseProvider.cs @@ -13,7 +13,6 @@ using AIStudio.Settings; using AIStudio.Tools.MIME; using AIStudio.Tools.PluginSystem; using AIStudio.Tools.Rust; -using AIStudio.Tools.Services; using Host = AIStudio.Provider.SelfHosted.Host; @@ -36,16 +35,6 @@ public abstract class BaseProvider : IProvider, ISecretId /// private readonly ILogger logger; - static BaseProvider() - { - RUST_SERVICE = Program.RUST_SERVICE; - ENCRYPTION = Program.ENCRYPTION; - } - - protected static readonly RustService RUST_SERVICE; - - protected static readonly Encryption ENCRYPTION; - protected static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new() { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, @@ -161,9 +150,9 @@ public abstract class BaseProvider : IProvider, ISecretId protected async Task GetModelLoadingSecretKey(SecretStoreType storeType, string? apiKeyProvisional = null, bool isTryingSecret = false) => apiKeyProvisional switch { not null => apiKeyProvisional, - _ => await RUST_SERVICE.GetAPIKey(this, storeType, isTrying: isTryingSecret) switch + _ => await Program.RUST_SERVICE.GetAPIKey(this, storeType, isTrying: isTryingSecret) switch { - { Success: true } result => await result.Secret.Decrypt(ENCRYPTION), + { Success: true } result => await result.Secret.Decrypt(Program.ENCRYPTION), _ => null, } }; @@ -981,7 +970,7 @@ public abstract class BaseProvider : IProvider, ISecretId where TAnnotation : IAnnotationStreamLine { // Get the API key: - var requestedSecret = await RUST_SERVICE.GetAPIKey(this, storeType, isTrying: isTryingSecret); + var requestedSecret = await Program.RUST_SERVICE.GetAPIKey(this, storeType, isTrying: isTryingSecret); if(!requestedSecret.Success && !isTryingSecret) yield break; @@ -1005,7 +994,7 @@ public abstract class BaseProvider : IProvider, ISecretId // Set the authorization header: if (requestedSecret.Success) - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION)); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(Program.ENCRYPTION)); // Set provider-specific headers: headersAction?.Invoke(request.Headers); @@ -1053,7 +1042,7 @@ public abstract class BaseProvider : IProvider, ISecretId { case LLMProviders.SELF_HOSTED: if(requestedSecret.Success) - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION)); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(Program.ENCRYPTION)); break; @@ -1064,7 +1053,7 @@ public abstract class BaseProvider : IProvider, ISecretId return TranscriptionResult.Failure(); } - request.Headers.Add("Authorization", await requestedSecret.Secret.Decrypt(ENCRYPTION)); + request.Headers.Add("Authorization", await requestedSecret.Secret.Decrypt(Program.ENCRYPTION)); break; default: @@ -1074,7 +1063,7 @@ public abstract class BaseProvider : IProvider, ISecretId return TranscriptionResult.Failure(); } - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION)); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(Program.ENCRYPTION)); break; } @@ -1135,7 +1124,7 @@ public abstract class BaseProvider : IProvider, ISecretId { case LLMProviders.SELF_HOSTED: if(requestedSecret.Success) - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION)); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(Program.ENCRYPTION)); break; @@ -1146,7 +1135,7 @@ public abstract class BaseProvider : IProvider, ISecretId return []; } - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION)); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(Program.ENCRYPTION)); break; } diff --git a/app/MindWork AI Studio/Provider/Fireworks/ProviderFireworks.cs b/app/MindWork AI Studio/Provider/Fireworks/ProviderFireworks.cs index a8840873..2554362b 100644 --- a/app/MindWork AI Studio/Provider/Fireworks/ProviderFireworks.cs +++ b/app/MindWork AI Studio/Provider/Fireworks/ProviderFireworks.cs @@ -63,7 +63,7 @@ public class ProviderFireworks() : BaseProvider(LLMProviders.FIREWORKS, new Uri( /// public override async Task TranscribeAudioAsync(Model transcriptionModel, string audioFilePath, SettingsManager settingsManager, CancellationToken token = default) { - var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.TRANSCRIPTION_PROVIDER); + var requestedSecret = await Program.RUST_SERVICE.GetAPIKey(this, SecretStoreType.TRANSCRIPTION_PROVIDER); return await this.PerformStandardTranscriptionRequest(requestedSecret, transcriptionModel, audioFilePath, token: token); } diff --git a/app/MindWork AI Studio/Provider/GWDG/ProviderGWDG.cs b/app/MindWork AI Studio/Provider/GWDG/ProviderGWDG.cs index f6181c72..eecbfd0d 100644 --- a/app/MindWork AI Studio/Provider/GWDG/ProviderGWDG.cs +++ b/app/MindWork AI Studio/Provider/GWDG/ProviderGWDG.cs @@ -62,7 +62,7 @@ public sealed class ProviderGWDG() : BaseProvider(LLMProviders.GWDG, new Uri("ht /// public override async Task TranscribeAudioAsync(Model transcriptionModel, string audioFilePath, SettingsManager settingsManager, CancellationToken token = default) { - var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.TRANSCRIPTION_PROVIDER); + var requestedSecret = await Program.RUST_SERVICE.GetAPIKey(this, SecretStoreType.TRANSCRIPTION_PROVIDER); return await this.PerformStandardTranscriptionRequest(requestedSecret, transcriptionModel, audioFilePath, token: token); } diff --git a/app/MindWork AI Studio/Provider/Google/ProviderGoogle.cs b/app/MindWork AI Studio/Provider/Google/ProviderGoogle.cs index 5e12811e..b4ad418d 100644 --- a/app/MindWork AI Studio/Provider/Google/ProviderGoogle.cs +++ b/app/MindWork AI Studio/Provider/Google/ProviderGoogle.cs @@ -71,7 +71,7 @@ public class ProviderGoogle() : BaseProvider(LLMProviders.GOOGLE, new Uri("https /// public override async Task>> EmbedTextAsync(Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) { - var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.EMBEDDING_PROVIDER); + var requestedSecret = await Program.RUST_SERVICE.GetAPIKey(this, SecretStoreType.EMBEDDING_PROVIDER); try { var modelName = embeddingModel.Id; @@ -104,7 +104,7 @@ public class ProviderGoogle() : BaseProvider(LLMProviders.GOOGLE, new Uri("https var embeddingRequest = JsonSerializer.Serialize(payload, JSON_SERIALIZER_OPTIONS); var embedUrl = $"https://generativelanguage.googleapis.com/v1beta/models/{modelName}:embedContent"; using var request = new HttpRequestMessage(HttpMethod.Post, embedUrl); - request.Headers.Add("x-goog-api-key", await requestedSecret.Secret.Decrypt(ENCRYPTION)); + request.Headers.Add("x-goog-api-key", await requestedSecret.Secret.Decrypt(Program.ENCRYPTION)); // Set the content: request.Content = new StringContent(embeddingRequest, Encoding.UTF8, "application/json"); diff --git a/app/MindWork AI Studio/Provider/Helmholtz/ProviderHelmholtz.cs b/app/MindWork AI Studio/Provider/Helmholtz/ProviderHelmholtz.cs index bc6647d2..8cb756c6 100644 --- a/app/MindWork AI Studio/Provider/Helmholtz/ProviderHelmholtz.cs +++ b/app/MindWork AI Studio/Provider/Helmholtz/ProviderHelmholtz.cs @@ -70,7 +70,7 @@ public sealed class ProviderHelmholtz() : BaseProvider(LLMProviders.HELMHOLTZ, n /// public override async Task>> EmbedTextAsync(Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) { - var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.EMBEDDING_PROVIDER); + var requestedSecret = await Program.RUST_SERVICE.GetAPIKey(this, SecretStoreType.EMBEDDING_PROVIDER); return await this.PerformStandardTextEmbeddingRequest(requestedSecret, embeddingModel, token: token, texts: texts); } diff --git a/app/MindWork AI Studio/Provider/Mistral/ProviderMistral.cs b/app/MindWork AI Studio/Provider/Mistral/ProviderMistral.cs index c4169b72..c3024ab5 100644 --- a/app/MindWork AI Studio/Provider/Mistral/ProviderMistral.cs +++ b/app/MindWork AI Studio/Provider/Mistral/ProviderMistral.cs @@ -69,14 +69,14 @@ public sealed class ProviderMistral() : BaseProvider(LLMProviders.MISTRAL, new U /// public override async Task TranscribeAudioAsync(Provider.Model transcriptionModel, string audioFilePath, SettingsManager settingsManager, CancellationToken token = default) { - var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.TRANSCRIPTION_PROVIDER); + var requestedSecret = await Program.RUST_SERVICE.GetAPIKey(this, SecretStoreType.TRANSCRIPTION_PROVIDER); return await this.PerformStandardTranscriptionRequest(requestedSecret, transcriptionModel, audioFilePath, token: token); } /// public override async Task>> EmbedTextAsync(Provider.Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) { - var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.EMBEDDING_PROVIDER); + var requestedSecret = await Program.RUST_SERVICE.GetAPIKey(this, SecretStoreType.EMBEDDING_PROVIDER); return await this.PerformStandardTextEmbeddingRequest(requestedSecret, embeddingModel, token: token, texts: texts); } diff --git a/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs b/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs index 56744f91..2c5132d1 100644 --- a/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs +++ b/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs @@ -56,7 +56,7 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, new Ur public override async IAsyncEnumerable StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default) { // Get the API key: - var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER); + var requestedSecret = await Program.RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER); if(!requestedSecret.Success) yield break; @@ -221,7 +221,7 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, new Ur var request = new HttpRequestMessage(HttpMethod.Post, requestPath); // Set the authorization header: - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION)); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(Program.ENCRYPTION)); // Set the content: request.Content = new StringContent(openAIChatRequest, Encoding.UTF8, "application/json"); @@ -250,14 +250,14 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, new Ur /// public override async Task TranscribeAudioAsync(Model transcriptionModel, string audioFilePath, SettingsManager settingsManager, CancellationToken token = default) { - var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.TRANSCRIPTION_PROVIDER); + var requestedSecret = await Program.RUST_SERVICE.GetAPIKey(this, SecretStoreType.TRANSCRIPTION_PROVIDER); return await this.PerformStandardTranscriptionRequest(requestedSecret, transcriptionModel, audioFilePath, token: token); } /// public override async Task>> EmbedTextAsync(Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) { - var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.EMBEDDING_PROVIDER); + var requestedSecret = await Program.RUST_SERVICE.GetAPIKey(this, SecretStoreType.EMBEDDING_PROVIDER); return await this.PerformStandardTextEmbeddingRequest(requestedSecret, embeddingModel, token: token, texts: texts); } diff --git a/app/MindWork AI Studio/Provider/OpenRouter/ProviderOpenRouter.cs b/app/MindWork AI Studio/Provider/OpenRouter/ProviderOpenRouter.cs index 6e09ef02..5a7044b7 100644 --- a/app/MindWork AI Studio/Provider/OpenRouter/ProviderOpenRouter.cs +++ b/app/MindWork AI Studio/Provider/OpenRouter/ProviderOpenRouter.cs @@ -79,7 +79,7 @@ public sealed class ProviderOpenRouter() : BaseProvider(LLMProviders.OPEN_ROUTER /// public override async Task>> EmbedTextAsync(Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) { - var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.EMBEDDING_PROVIDER); + var requestedSecret = await Program.RUST_SERVICE.GetAPIKey(this, SecretStoreType.EMBEDDING_PROVIDER); return await this.PerformStandardTextEmbeddingRequest(requestedSecret, embeddingModel, token: token, texts: texts); } diff --git a/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs b/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs index cf3b858a..53ee6db8 100644 --- a/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs +++ b/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs @@ -75,14 +75,14 @@ public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvide /// public override async Task TranscribeAudioAsync(Provider.Model transcriptionModel, string audioFilePath, SettingsManager settingsManager, CancellationToken token = default) { - var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.TRANSCRIPTION_PROVIDER, isTrying: true); + var requestedSecret = await Program.RUST_SERVICE.GetAPIKey(this, SecretStoreType.TRANSCRIPTION_PROVIDER, isTrying: true); return await this.PerformStandardTranscriptionRequest(requestedSecret, transcriptionModel, audioFilePath, host, token); } /// public override async Task>> EmbedTextAsync(Provider.Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) { - var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.EMBEDDING_PROVIDER, isTrying: true); + var requestedSecret = await Program.RUST_SERVICE.GetAPIKey(this, SecretStoreType.EMBEDDING_PROVIDER, isTrying: true); return await this.PerformStandardTextEmbeddingRequest(requestedSecret, embeddingModel, host, token: token, texts: texts); } diff --git a/app/MindWork AI Studio/Settings/ConfigMeta.cs b/app/MindWork AI Studio/Settings/ConfigMeta.cs index 46a248b3..8c597906 100644 --- a/app/MindWork AI Studio/Settings/ConfigMeta.cs +++ b/app/MindWork AI Studio/Settings/ConfigMeta.cs @@ -151,7 +151,7 @@ public record ConfigMeta : ConfigMetaBase /// 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 : ConfigMetaBase /// The value to set for the configuration property. 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 : ConfigMetaBase /// 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; diff --git a/app/MindWork AI Studio/Settings/ConfigMetaBase.cs b/app/MindWork AI Studio/Settings/ConfigMetaBase.cs index 4ef74e88..d077a701 100644 --- a/app/MindWork AI Studio/Settings/ConfigMetaBase.cs +++ b/app/MindWork AI Studio/Settings/ConfigMetaBase.cs @@ -2,5 +2,5 @@ namespace AIStudio.Settings; public abstract record ConfigMetaBase : IConfig { - protected static readonly SettingsManager SETTINGS_MANAGER = Program.SERVICE_PROVIDER.GetRequiredService(); + protected static SettingsManager SettingsManagerAccess => Program.SERVICE_PROVIDER.GetRequiredService(); } \ No newline at end of file diff --git a/app/MindWork AI Studio/Settings/ManagedConfiguration.Parsing.cs b/app/MindWork AI Studio/Settings/ManagedConfiguration.Parsing.cs index 4b453d27..e9dd8b41 100644 --- a/app/MindWork AI Studio/Settings/ManagedConfiguration.Parsing.cs +++ b/app/MindWork AI Studio/Settings/ManagedConfiguration.Parsing.cs @@ -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(currentValue); merged.UnionWith(configuredValue); diff --git a/app/MindWork AI Studio/Settings/ManagedConfiguration.cs b/app/MindWork AI Studio/Settings/ManagedConfiguration.cs index 0e62f2c6..84607ed0 100644 --- a/app/MindWork AI Studio/Settings/ManagedConfiguration.cs +++ b/app/MindWork AI Studio/Settings/ManagedConfiguration.cs @@ -9,7 +9,7 @@ namespace AIStudio.Settings; public static partial class ManagedConfiguration { private static readonly ConcurrentDictionary METADATA = new(); - private static readonly SettingsManager SETTINGS_MANAGER = Program.SERVICE_PROVIDER.GetRequiredService(); + private static SettingsManager SettingsManagerAccess => Program.SERVICE_PROVIDER.GetRequiredService(); /// /// Attempts to retrieve the configuration metadata for a given configuration selection and @@ -418,19 +418,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( ConfigMeta configMeta, diff --git a/app/MindWork AI Studio/Settings/SettingsManager.cs b/app/MindWork AI Studio/Settings/SettingsManager.cs index 3ec8906c..76be18d2 100644 --- a/app/MindWork AI Studio/Settings/SettingsManager.cs +++ b/app/MindWork AI Studio/Settings/SettingsManager.cs @@ -348,17 +348,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 +363,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 +372,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) diff --git a/app/MindWork AI Studio/Tools/ComponentsExtensions.cs b/app/MindWork AI Studio/Tools/ComponentsExtensions.cs index bd48dbc5..b95ab1cb 100644 --- a/app/MindWork AI Studio/Tools/ComponentsExtensions.cs +++ b/app/MindWork AI Studio/Tools/ComponentsExtensions.cs @@ -169,7 +169,7 @@ public static class ComponentsExtensions public static ChatTemplate PreselectedChatTemplate(this Components component, SettingsManager settingsManager) => component switch { - Components.CHAT => settingsManager.ConfigurationData.Chat.PreselectOptions ? settingsManager.ConfigurationData.ChatTemplates.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.Chat.PreselectedChatTemplate) ?? ChatTemplate.NO_CHAT_TEMPLATE : ChatTemplate.NO_CHAT_TEMPLATE, + Components.CHAT => settingsManager.ConfigurationData.Chat.PreselectOptions ? settingsManager.GetChatTemplateById(settingsManager.ConfigurationData.Chat.PreselectedChatTemplate) : ChatTemplate.NO_CHAT_TEMPLATE, _ => ChatTemplate.NO_CHAT_TEMPLATE, }; diff --git a/app/MindWork AI Studio/Tools/ExternalHttpClientTimeout.cs b/app/MindWork AI Studio/Tools/ExternalHttpClientTimeout.cs index 3d465737..de13f40d 100644 --- a/app/MindWork AI Studio/Tools/ExternalHttpClientTimeout.cs +++ b/app/MindWork AI Studio/Tools/ExternalHttpClientTimeout.cs @@ -26,7 +26,7 @@ public static class ExternalHttpClientTimeout private static string TB(string fallbackEN) => PluginSystem.I18N.I.T(fallbackEN, typeof(ExternalHttpClientTimeout).Namespace, nameof(ExternalHttpClientTimeout)); private static readonly Lazy LOGGER = new(() => Program.LOGGER_FACTORY.CreateLogger(nameof(ExternalHttpClientTimeout))); - private static readonly Lazy SETTINGS_MANAGER = new(() => Program.SERVICE_PROVIDER.GetRequiredService()); + private static SettingsManager SettingsManagerAccess => Program.SERVICE_PROVIDER.GetRequiredService(); private static readonly Lock CUSTOM_ROOT_CERTIFICATE_LOCK = new(); private static CustomRootCertificateCache? CUSTOM_ROOT_CERTIFICATE_CACHE; @@ -91,7 +91,7 @@ public static class ExternalHttpClientTimeout private static TimeSpan GetTimeout() { - var seconds = SETTINGS_MANAGER.Value.ConfigurationData.App.HttpClientTimeoutSeconds; + var seconds = SettingsManagerAccess.ConfigurationData.App.HttpClientTimeoutSeconds; if (seconds <= 0) seconds = DEFAULT_HTTP_CLIENT_TIMEOUT_SECONDS; @@ -129,11 +129,11 @@ public static class ExternalHttpClientTimeout var enabled = TryParseBooleanEnvironmentValue(envEnabled, out var parsedEnvEnabled) ? parsedEnvEnabled - : SETTINGS_MANAGER.Value.ConfigurationData.App.ExternalHttpCustomRootCertificatesEnabled; + : SettingsManagerAccess.ConfigurationData.App.ExternalHttpCustomRootCertificatesEnabled; var bundlePath = !string.IsNullOrWhiteSpace(envBundlePath) ? envBundlePath.Trim() - : SETTINGS_MANAGER.Value.ConfigurationData.App.ExternalHttpCustomRootCertificateBundlePath.Trim(); + : SettingsManagerAccess.ConfigurationData.App.ExternalHttpCustomRootCertificateBundlePath.Trim(); var allowedHostPatterns = ReadAllowedHostPatterns(envAllowedHosts); var source = ReadCustomRootCertificateConfigurationSource(envEnabled, envBundlePath, envAllowedHosts); @@ -158,7 +158,7 @@ public static class ExternalHttpClientTimeout { IEnumerable rawPatterns = !string.IsNullOrWhiteSpace(envAllowedHosts) ? envAllowedHosts.Split([';', ','], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) - : SETTINGS_MANAGER.Value.ConfigurationData.App.ExternalHttpCustomRootCertificateAllowedHosts; + : SettingsManagerAccess.ConfigurationData.App.ExternalHttpCustomRootCertificateAllowedHosts; var patterns = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (var rawPattern in rawPatterns) diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs index 11116fd9..8bd55c93 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs @@ -9,7 +9,7 @@ namespace AIStudio.Tools.PluginSystem; public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginType type) : PluginBase(isInternal, state, type) { private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(PluginConfiguration).Namespace, nameof(PluginConfiguration)); - private static readonly SettingsManager SETTINGS_MANAGER = Program.SERVICE_PROVIDER.GetRequiredService(); + private static SettingsManager SettingsManagerAccess => Program.SERVICE_PROVIDER.GetRequiredService(); private static readonly ILogger LOG = Program.LOGGER_FACTORY.CreateLogger(nameof(PluginConfiguration)); private List configObjects = []; @@ -41,7 +41,7 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT await StoreEnterpriseApiKeysAsync(); await StoreEnterpriseSecretsAsync(); - await SETTINGS_MANAGER.StoreSettings(); + await SettingsManagerAccess.StoreSettings(); await MessageBus.INSTANCE.SendMessage(null, Event.CONFIGURATION_CHANGED); } } diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs index 934de5dc..620ca5a7 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs @@ -14,8 +14,9 @@ namespace AIStudio.Tools.PluginSystem; /// public sealed record PluginConfigurationObject { - private static readonly RustService RUST_SERVICE = Program.SERVICE_PROVIDER.GetRequiredService(); - private static readonly SettingsManager SETTINGS_MANAGER = Program.SERVICE_PROVIDER.GetRequiredService(); + private static RustService RustService => Program.SERVICE_PROVIDER.GetRequiredService(); + private static SettingsManager SettingsManagerAccess => Program.SERVICE_PROVIDER.GetRequiredService(); + private static ThreadSafeRandom Rng => Program.SERVICE_PROVIDER.GetRequiredService(); private static readonly ILogger LOG = Program.LOGGER_FACTORY.CreateLogger(); /// @@ -91,7 +92,8 @@ public sealed record PluginConfigurationObject return false; } - var storedObjects = configObjectSelection.Compile()(SETTINGS_MANAGER.ConfigurationData); + var localSettingsManager = SettingsManagerAccess; + var storedObjects = configObjectSelection.Compile()(localSettingsManager.ConfigurationData); var numberObjects = luaTable.ArrayLength; ThreadSafeRandom? random = null; for (var i = 1; i <= numberObjects; i++) @@ -141,7 +143,7 @@ public sealed record PluginConfigurationObject // Case: The object does not exist, we have to add it else { - if (nextConfigObjectNumSelection.TryIncrement(SETTINGS_MANAGER.ConfigurationData, IncrementType.POST) is { Success: true, UpdatedValue: var nextNum }) + if (nextConfigObjectNumSelection.TryIncrement(localSettingsManager.ConfigurationData, IncrementType.POST) is { Success: true, UpdatedValue: var nextNum }) { // Case: Increment the next number was successful configObject = configObject with { Num = nextNum }; @@ -150,7 +152,7 @@ public sealed record PluginConfigurationObject else { // Case: The next number could not be incremented, we use a random number - random ??= new ThreadSafeRandom(); + random ??= Rng; configObject = configObject with { Num = (uint)random.Next(500_000, 1_000_000) }; storedObjects.Add((TClass)configObject); LOG.LogWarning("The next number for the configuration object '{ConfigObjectName}' (id={ConfigObjectId}) could not be incremented. Using a random number instead (config plugin id: {ConfigPluginId}).", configObject.Name, configObject.Id, configPluginId); @@ -185,7 +187,8 @@ public sealed record PluginConfigurationObject return false; } - var storedObjects = SETTINGS_MANAGER.ConfigurationData.DataSources; + var localSettingsManager = SettingsManagerAccess; + var storedObjects = localSettingsManager.ConfigurationData.DataSources; var numberObjects = luaTable.ArrayLength; ThreadSafeRandom? random = null; for (var i = 1; i <= numberObjects; i++) @@ -222,14 +225,14 @@ public sealed record PluginConfigurationObject } else { - if (IncrementDataSourceNum() is { Success: true, UpdatedValue: var nextNum }) + if (IncrementDataSourceNum(localSettingsManager.ConfigurationData) is { Success: true, UpdatedValue: var nextNum }) { configObject = configObject with { Num = nextNum }; storedObjects.Add(configObject); } else { - random ??= new ThreadSafeRandom(); + random ??= Rng; configObject = configObject with { Num = (uint)random.Next(500_000, 1_000_000) }; storedObjects.Add(configObject); LOG.LogWarning("The next number for the data source '{ConfigObjectName}' (id={ConfigObjectId}) could not be incremented. Using a random number instead (config plugin id: {ConfigPluginId}).", configObject.Name, configObject.Id, configPluginId); @@ -239,9 +242,9 @@ public sealed record PluginConfigurationObject return true; - static IncrementResult IncrementDataSourceNum() + static IncrementResult IncrementDataSourceNum(Data data) { - return ((Expression>)(x => x.NextDataSourceNum)).TryIncrement(SETTINGS_MANAGER.ConfigurationData, IncrementType.POST); + return ((Expression>)(x => x.NextDataSourceNum)).TryIncrement(data, IncrementType.POST); } } @@ -264,7 +267,8 @@ public sealed record PluginConfigurationObject SecretStoreType? secretStoreType = null, bool deleteSecret = false) where TClass : IConfigurationObject { - var configuredObjects = configObjectSelection.Compile()(SETTINGS_MANAGER.ConfigurationData); + var localSettingsManager = SettingsManagerAccess; + var configuredObjects = configObjectSelection.Compile()(localSettingsManager.ConfigurationData); var leftOverObjects = new List(); foreach (var configuredObject in configuredObjects) { @@ -307,7 +311,7 @@ public sealed record PluginConfigurationObject // Delete the API key from the OS keyring if the removed object has one: if(deleteSecret && item is ISecretId regularSecretId) { - var deleteResult = await RUST_SERVICE.DeleteSecret(regularSecretId, secretStoreType ?? SecretStoreType.DATA_SOURCE); + var deleteResult = await RustService.DeleteSecret(regularSecretId, secretStoreType ?? SecretStoreType.DATA_SOURCE); if (deleteResult.Success) LOG.LogInformation($"Successfully deleted secret for removed enterprise object '{item.Name}' from the OS keyring."); else @@ -315,7 +319,7 @@ public sealed record PluginConfigurationObject } else if(secretStoreType is not null && item is ISecretId secretId) { - var deleteResult = await RUST_SERVICE.DeleteAPIKey(secretId, secretStoreType.Value); + var deleteResult = await RustService.DeleteAPIKey(secretId, secretStoreType.Value); if (deleteResult.Success) LOG.LogInformation($"Successfully deleted API key for removed enterprise provider '{item.Name}' from the OS keyring."); else diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs index e076a842..f7a5aabf 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs @@ -191,7 +191,7 @@ public static partial class PluginFactory wasConfigurationChanged = true; // Check left-over mandatory info acceptances: - if (SETTINGS_MANAGER.ConfigurationData.MandatoryInformation.RemoveLeftOverAcceptances(GetMandatoryInfos())) + if (SettingsManagerAccess.ConfigurationData.MandatoryInformation.RemoveLeftOverAcceptances(GetMandatoryInfos())) wasConfigurationChanged = true; // Check for a preselected provider: @@ -285,7 +285,7 @@ public static partial class PluginFactory if (wasConfigurationChanged) { - await SETTINGS_MANAGER.StoreSettings(); + await SettingsManagerAccess.StoreSettings(); await MessageBus.INSTANCE.SendMessage(null, Event.CONFIGURATION_CHANGED); } } diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Starting.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Starting.cs index 04bf73e3..3df2e224 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Starting.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Starting.cs @@ -64,7 +64,7 @@ public static partial class PluginFactory try { - if (availablePlugin.IsInternal || SETTINGS_MANAGER.IsPluginEnabled(availablePlugin) || availablePlugin.Type == PluginType.CONFIGURATION || availablePlugin.Type == PluginType.ASSISTANT) + if (availablePlugin.IsInternal || SettingsManagerAccess.IsPluginEnabled(availablePlugin) || availablePlugin.Type == PluginType.CONFIGURATION || availablePlugin.Type == PluginType.ASSISTANT) if(await Start(availablePlugin, cancellationToken) is { IsValid: true } plugin) { if (plugin is PluginConfiguration configPlugin) diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs index a707ab06..ce0ae866 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs @@ -6,7 +6,7 @@ namespace AIStudio.Tools.PluginSystem; public static partial class PluginFactory { private static readonly ILogger LOG = Program.LOGGER_FACTORY.CreateLogger(nameof(PluginFactory)); - private static readonly SettingsManager SETTINGS_MANAGER = Program.SERVICE_PROVIDER.GetRequiredService(); + private static SettingsManager SettingsManagerAccess => Program.SERVICE_PROVIDER.GetRequiredService(); private static string DATA_DIR = string.Empty; private static string PLUGINS_ROOT = string.Empty; diff --git a/app/MindWork AI Studio/Tools/RAG/IRetrievalContextExtensions.cs b/app/MindWork AI Studio/Tools/RAG/IRetrievalContextExtensions.cs index 74ff4e58..24b1d24e 100644 --- a/app/MindWork AI Studio/Tools/RAG/IRetrievalContextExtensions.cs +++ b/app/MindWork AI Studio/Tools/RAG/IRetrievalContextExtensions.cs @@ -6,7 +6,7 @@ namespace AIStudio.Tools.RAG; public static class IRetrievalContextExtensions { - private static readonly ILogger LOGGER = Program.SERVICE_PROVIDER.GetService>()!; + private static readonly ILogger LOGGER = Program.LOGGER_FACTORY.CreateLogger(); public static async Task AsMarkdown(this IReadOnlyList retrievalContexts, StringBuilder? sb = null, CancellationToken token = default) { diff --git a/app/MindWork AI Studio/Tools/Services/TemporaryChatService.cs b/app/MindWork AI Studio/Tools/Services/TemporaryChatService.cs index 90203b2b..3da98ff4 100644 --- a/app/MindWork AI Studio/Tools/Services/TemporaryChatService.cs +++ b/app/MindWork AI Studio/Tools/Services/TemporaryChatService.cs @@ -17,7 +17,6 @@ public sealed class TemporaryChatService(ILogger logger, S logger.LogInformation("The temporary chat maintenance service was initialized."); - await settingsManager.LoadSettings(); if(settingsManager.ConfigurationData.Workspace.StorageTemporaryMaintenancePolicy is WorkspaceStorageTemporaryMaintenancePolicy.NO_AUTOMATIC_MAINTENANCE) { logger.LogWarning("Automatic maintenance of temporary chat storage is disabled. Exiting maintenance service."); diff --git a/app/MindWork AI Studio/wwwroot/changelog/v26.6.1.md b/app/MindWork AI Studio/wwwroot/changelog/v26.6.1.md index d3fd4a57..6bbb8f6b 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v26.6.1.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v26.6.1.md @@ -10,6 +10,8 @@ - Improved workspaces by allowing new workspaces to be created while moving a chat. - Improved voice recording shortcut labels so they match the user's keyboard layout after being configured. - Improved the enterprise configuration details on the information page by showing where each configuration comes from and which configuration slot was used. +- Fixed an issue where newly added profiles and chat templates were not usable until the app was restarted. +- Fixed an issue where renamed chat templates and profiles continued to show their old names in the chat toolbar until the app was restarted. - Fixed workspace creation and renaming to prevent new workspaces from using an existing name. - Fixed an issue on Microsoft Windows where reading attached documents could briefly open a terminal window while processing files. - Upgraded dependencies. \ No newline at end of file diff --git a/app/SourceCodeRules/SourceCodeRules/AnalyzerReleases.Shipped.md b/app/SourceCodeRules/SourceCodeRules/AnalyzerReleases.Shipped.md index e6f97e74..2d96342e 100644 --- a/app/SourceCodeRules/SourceCodeRules/AnalyzerReleases.Shipped.md +++ b/app/SourceCodeRules/SourceCodeRules/AnalyzerReleases.Shipped.md @@ -11,4 +11,5 @@ MWAIS0005 | Usage | Error | ThisUsageAnalyzer MWAIS0006 | Style | Error | SwitchExpressionMethodAnalyzer MWAIS0007 | Usage | Error | EmptyStringAnalyzer - MWAIS0008 | Naming | Error | LocalConstantsAnalyzer \ No newline at end of file + MWAIS0008 | Naming | Error | LocalConstantsAnalyzer + MWAIS0009 | Usage | Error | StaticServiceProviderCacheAnalyzer \ No newline at end of file diff --git a/app/SourceCodeRules/SourceCodeRules/Identifier.cs b/app/SourceCodeRules/SourceCodeRules/Identifier.cs index aa782cf9..ae9e3b57 100644 --- a/app/SourceCodeRules/SourceCodeRules/Identifier.cs +++ b/app/SourceCodeRules/SourceCodeRules/Identifier.cs @@ -10,4 +10,5 @@ public static class Identifier public const string SWITCH_EXPRESSION_METHOD_ANALYZER = $"{Tools.ID_PREFIX}0006"; public const string EMPTY_STRING_ANALYZER = $"{Tools.ID_PREFIX}0007"; public const string LOCAL_CONSTANTS_ANALYZER = $"{Tools.ID_PREFIX}0008"; + public const string STATIC_SERVICE_PROVIDER_CACHE_ANALYZER = $"{Tools.ID_PREFIX}0009"; } \ No newline at end of file diff --git a/app/SourceCodeRules/SourceCodeRules/UsageAnalyzers/StaticServiceProviderCacheAnalyzer.cs b/app/SourceCodeRules/SourceCodeRules/UsageAnalyzers/StaticServiceProviderCacheAnalyzer.cs new file mode 100644 index 00000000..4cf823db --- /dev/null +++ b/app/SourceCodeRules/SourceCodeRules/UsageAnalyzers/StaticServiceProviderCacheAnalyzer.cs @@ -0,0 +1,159 @@ +using System.Collections.Immutable; + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace SourceCodeRules.UsageAnalyzers; + +#pragma warning disable RS1038 +[DiagnosticAnalyzer(LanguageNames.CSharp)] +#pragma warning restore RS1038 +public sealed class StaticServiceProviderCacheAnalyzer : DiagnosticAnalyzer +{ + private const string DIAGNOSTIC_ID = Identifier.STATIC_SERVICE_PROVIDER_CACHE_ANALYZER; + + private static readonly string TITLE = "Services from Program.SERVICE_PROVIDER must not be cached in static state"; + + private static readonly string MESSAGE_FORMAT = "Do not cache services from Program.SERVICE_PROVIDER in static state. Use constructor injection, method-local resolution, or a non-caching get-only property."; + + private static readonly string DESCRIPTION = MESSAGE_FORMAT; + + private const string CATEGORY = "Usage"; + + private static readonly DiagnosticDescriptor RULE = new(DIAGNOSTIC_ID, TITLE, MESSAGE_FORMAT, CATEGORY, DiagnosticSeverity.Error, isEnabledByDefault: true, description: DESCRIPTION); + + public override ImmutableArray SupportedDiagnostics => [RULE]; + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterSyntaxNodeAction(this.AnalyzeFieldDeclaration, SyntaxKind.FieldDeclaration); + context.RegisterSyntaxNodeAction(this.AnalyzeVariableDeclarator, SyntaxKind.VariableDeclarator); + context.RegisterSyntaxNodeAction(this.AnalyzePropertyDeclaration, SyntaxKind.PropertyDeclaration); + context.RegisterSyntaxNodeAction(this.AnalyzeAssignmentExpression, SyntaxKind.SimpleAssignmentExpression); + } + + private void AnalyzeFieldDeclaration(SyntaxNodeAnalysisContext context) + { + var fieldDeclaration = (FieldDeclarationSyntax)context.Node; + foreach (var variable in fieldDeclaration.Declaration.Variables) + this.AnalyzeStaticFieldInitializer(context, variable); + } + + private void AnalyzeVariableDeclarator(SyntaxNodeAnalysisContext context) + { + var variable = (VariableDeclaratorSyntax)context.Node; + if (variable.Parent?.Parent is FieldDeclarationSyntax) + return; + + this.AnalyzeStaticFieldInitializer(context, variable); + } + + private void AnalyzePropertyDeclaration(SyntaxNodeAnalysisContext context) + { + var propertyDeclaration = (PropertyDeclarationSyntax)context.Node; + if (propertyDeclaration.Initializer is null) + return; + + if (context.SemanticModel.GetDeclaredSymbol(propertyDeclaration) is not { IsStatic: true }) + return; + + if (!this.IsProgramServiceProviderGetCall(propertyDeclaration.Initializer.Value)) + return; + + var diagnostic = Diagnostic.Create(RULE, propertyDeclaration.Initializer.Value.GetLocation()); + context.ReportDiagnostic(diagnostic); + } + + private void AnalyzeAssignmentExpression(SyntaxNodeAnalysisContext context) + { + var assignment = (AssignmentExpressionSyntax)context.Node; + if (!this.IsProgramServiceProviderGetCall(assignment.Right)) + return; + + var targetSymbol = context.SemanticModel.GetSymbolInfo(assignment.Left).Symbol; + if (targetSymbol is not IFieldSymbol { IsStatic: true } && targetSymbol is not IPropertySymbol { IsStatic: true }) + return; + + var diagnostic = Diagnostic.Create(RULE, assignment.Right.GetLocation()); + context.ReportDiagnostic(diagnostic); + } + + private void AnalyzeStaticFieldInitializer(SyntaxNodeAnalysisContext context, VariableDeclaratorSyntax variable) + { + if (variable.Initializer is null) + return; + + if (context.SemanticModel.GetDeclaredSymbol(variable) is not IFieldSymbol { IsStatic: true }) + return; + + if (!this.IsProgramServiceProviderGetCall(variable.Initializer.Value)) + return; + + var diagnostic = Diagnostic.Create(RULE, variable.Initializer.Value.GetLocation()); + context.ReportDiagnostic(diagnostic); + } + + private bool IsProgramServiceProviderGetCall(ExpressionSyntax expression) + { + if (this.UnwrapSimpleExpression(expression) is not InvocationExpressionSyntax invocation) + return false; + + if (this.UnwrapSimpleExpression(invocation.Expression) is not MemberAccessExpressionSyntax memberAccess) + return false; + + if (!this.IsServiceProviderGetMethod(memberAccess.Name)) + return false; + + return this.IsProgramServiceProviderAccess(memberAccess.Expression); + } + + private bool IsServiceProviderGetMethod(SimpleNameSyntax name) => name switch + { + GenericNameSyntax genericName when genericName.TypeArgumentList.Arguments.Count == 1 => + genericName.Identifier.Text is "GetService" or "GetRequiredService", + _ => false, + }; + + private bool IsProgramServiceProviderAccess(ExpressionSyntax expression) + { + if (this.UnwrapSimpleExpression(expression) is not MemberAccessExpressionSyntax memberAccess) + return false; + + if (memberAccess.Name.Identifier.Text != "SERVICE_PROVIDER") + return false; + + return this.UnwrapSimpleExpression(memberAccess.Expression) is IdentifierNameSyntax { Identifier.Text: "Program" }; + } + + private ExpressionSyntax UnwrapSimpleExpression(ExpressionSyntax expression) + { + while (true) + { + switch (expression) + { + case ParenthesizedExpressionSyntax parenthesized: + expression = parenthesized.Expression; + continue; + + case PostfixUnaryExpressionSyntax { RawKind: (int)SyntaxKind.SuppressNullableWarningExpression } postfixUnary: + expression = postfixUnary.Operand; + continue; + + case CastExpressionSyntax castExpression: + expression = castExpression.Expression; + continue; + + case BinaryExpressionSyntax { RawKind: (int)SyntaxKind.AsExpression } asExpression: + expression = asExpression.Left; + continue; + + default: + return expression; + } + } + } +} \ No newline at end of file