From 418f458118402d41b66ca629780fdba59452d202 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 4 May 2024 10:55:00 +0200 Subject: [PATCH] Added documentation --- .../CommonDialogs/ConfirmDialog.razor.cs | 3 ++ .../Components/Layout/MainLayout.razor.cs | 6 +++ .../Components/Pages/Chat.razor.cs | 3 ++ .../Components/Pages/Settings.razor.cs | 4 ++ app/MindWork AI Studio/Provider/IProvider.cs | 10 ++++ app/MindWork AI Studio/Provider/Providers.cs | 16 ++++++ app/MindWork AI Studio/Settings/Data.cs | 12 ++++- app/MindWork AI Studio/Settings/Provider.cs | 23 +++++++- .../Settings/ProviderDialog.razor.cs | 31 ++++++++++- .../Settings/SettingsManager.cs | 53 +++++++++++++++++++ app/MindWork AI Studio/Settings/Version.cs | 4 ++ 11 files changed, 162 insertions(+), 3 deletions(-) diff --git a/app/MindWork AI Studio/Components/CommonDialogs/ConfirmDialog.razor.cs b/app/MindWork AI Studio/Components/CommonDialogs/ConfirmDialog.razor.cs index 201f906..5e367e1 100644 --- a/app/MindWork AI Studio/Components/CommonDialogs/ConfirmDialog.razor.cs +++ b/app/MindWork AI Studio/Components/CommonDialogs/ConfirmDialog.razor.cs @@ -4,6 +4,9 @@ using MudBlazor; namespace AIStudio.Components.CommonDialogs; +/// +/// A confirmation dialog that can be used to ask the user for confirmation. +/// public partial class ConfirmDialog : ComponentBase { [CascadingParameter] diff --git a/app/MindWork AI Studio/Components/Layout/MainLayout.razor.cs b/app/MindWork AI Studio/Components/Layout/MainLayout.razor.cs index 67050cc..819c370 100644 --- a/app/MindWork AI Studio/Components/Layout/MainLayout.razor.cs +++ b/app/MindWork AI Studio/Components/Layout/MainLayout.razor.cs @@ -13,8 +13,14 @@ public partial class MainLayout : LayoutComponentBase protected override async Task OnInitializedAsync() { + // + // We use the Tauri API (Rust) to get the data and config directories + // for this app. + // var dataDir = await this.JsRuntime.InvokeAsync("window.__TAURI__.path.appLocalDataDir"); var configDir = await this.JsRuntime.InvokeAsync("window.__TAURI__.path.appConfigDir"); + + // Store the directories in the settings manager: SettingsManager.ConfigDirectory = configDir; SettingsManager.DataDirectory = dataDir; diff --git a/app/MindWork AI Studio/Components/Pages/Chat.razor.cs b/app/MindWork AI Studio/Components/Pages/Chat.razor.cs index 83f6fbc..5dad1c7 100644 --- a/app/MindWork AI Studio/Components/Pages/Chat.razor.cs +++ b/app/MindWork AI Studio/Components/Pages/Chat.razor.cs @@ -2,6 +2,9 @@ using Microsoft.AspNetCore.Components; namespace AIStudio.Components.Pages; +/// +/// The chat page. +/// public partial class Chat : ComponentBase { private async Task SendMessage() diff --git a/app/MindWork AI Studio/Components/Pages/Settings.razor.cs b/app/MindWork AI Studio/Components/Pages/Settings.razor.cs index cabf3dd..5b7803a 100644 --- a/app/MindWork AI Studio/Components/Pages/Settings.razor.cs +++ b/app/MindWork AI Studio/Components/Pages/Settings.razor.cs @@ -37,6 +37,8 @@ public partial class Settings : ComponentBase #endregion + #region Provider related + private async Task AddProvider() { var dialogParameters = new DialogParameters @@ -96,4 +98,6 @@ public partial class Settings : ComponentBase await this.SettingsManager.StoreSettings(); } } + + #endregion } \ No newline at end of file diff --git a/app/MindWork AI Studio/Provider/IProvider.cs b/app/MindWork AI Studio/Provider/IProvider.cs index db138f7..38b90ef 100644 --- a/app/MindWork AI Studio/Provider/IProvider.cs +++ b/app/MindWork AI Studio/Provider/IProvider.cs @@ -3,10 +3,20 @@ using Microsoft.JSInterop; namespace AIStudio.Provider; +/// +/// A common interface for all providers. +/// public interface IProvider { + /// + /// The provider's ID. + /// public string Id { get; } + /// + /// The provider's instance name. Useful for multiple instances of the same provider, + /// e.g., to distinguish between different OpenAI API keys. + /// public string InstanceName { get; set; } public IAsyncEnumerable GetChatCompletion(IJSRuntime jsRuntime, SettingsManager settings, Model chatModel, Thread chatThread); diff --git a/app/MindWork AI Studio/Provider/Providers.cs b/app/MindWork AI Studio/Provider/Providers.cs index 22219d4..83d3ad9 100644 --- a/app/MindWork AI Studio/Provider/Providers.cs +++ b/app/MindWork AI Studio/Provider/Providers.cs @@ -2,14 +2,25 @@ using AIStudio.Provider.OpenAI; namespace AIStudio.Provider; +/// +/// Enum for all available providers. +/// public enum Providers { NONE, OPEN_AI, } +/// +/// Extension methods for the provider enum. +/// public static class ExtensionsProvider { + /// + /// Returns the human-readable name of the provider. + /// + /// The provider. + /// The human-readable name of the provider. public static string ToName(this Providers provider) => provider switch { Providers.OPEN_AI => "OpenAI", @@ -17,6 +28,11 @@ public static class ExtensionsProvider _ => "Unknown", }; + /// + /// Creates a new provider instance based on the provider value. + /// + /// The provider value. + /// The provider instance. public static IProvider CreateProvider(this Providers provider) => provider switch { Providers.OPEN_AI => new ProviderOpenAI(), diff --git a/app/MindWork AI Studio/Settings/Data.cs b/app/MindWork AI Studio/Settings/Data.cs index 16c1ffa..1cbeca3 100644 --- a/app/MindWork AI Studio/Settings/Data.cs +++ b/app/MindWork AI Studio/Settings/Data.cs @@ -1,8 +1,18 @@ namespace AIStudio.Settings; +/// +/// The data model for the settings file. +/// public sealed class Data { - public Version Version { get; init; } + /// + /// The version of the settings file. Allows us to upgrade the settings, + /// when a new version is available. + /// + public Version Version { get; init; } = Version.V1; + /// + /// List of configured providers. + /// public List Providers { get; init; } = new(); } \ No newline at end of file diff --git a/app/MindWork AI Studio/Settings/Provider.cs b/app/MindWork AI Studio/Settings/Provider.cs index eb7fdc8..4d32f01 100644 --- a/app/MindWork AI Studio/Settings/Provider.cs +++ b/app/MindWork AI Studio/Settings/Provider.cs @@ -2,4 +2,25 @@ using AIStudio.Provider; namespace AIStudio.Settings; -public readonly record struct Provider(string Id, string InstanceName, Providers UsedProvider); \ No newline at end of file +/// +/// Data model for configured providers. +/// +/// The provider's ID. +/// The provider's instance name. Useful for multiple instances of the same provider, e.g., to distinguish between different OpenAI API keys. +/// The provider used. +public readonly record struct Provider(string Id, string InstanceName, Providers UsedProvider) +{ + #region Overrides of ValueType + + /// + /// Returns a string that represents the current provider in a human-readable format. + /// We use this to display the provider in the chat UI. + /// + /// A string that represents the current provider in a human-readable format. + public override string ToString() + { + return $"{this.InstanceName} ({this.UsedProvider.ToName()})"; + } + + #endregion +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Settings/ProviderDialog.razor.cs b/app/MindWork AI Studio/Settings/ProviderDialog.razor.cs index b8c5fa8..51e602f 100644 --- a/app/MindWork AI Studio/Settings/ProviderDialog.razor.cs +++ b/app/MindWork AI Studio/Settings/ProviderDialog.razor.cs @@ -9,20 +9,35 @@ using MudBlazor; namespace AIStudio.Settings; +/// +/// The provider settings dialog. +/// public partial class ProviderDialog : ComponentBase { [CascadingParameter] private MudDialogInstance MudDialog { get; set; } = null!; + /// + /// The provider's ID. + /// [Parameter] public string DataId { get; set; } = Guid.NewGuid().ToString(); + /// + /// The user chosen instance name. + /// [Parameter] public string DataInstanceName { get; set; } = string.Empty; + /// + /// The provider to use. + /// [Parameter] public Providers DataProvider { get; set; } = Providers.NONE; - + + /// + /// Should the dialog be in editing mode? + /// [Parameter] public bool IsEditing { get; init; } @@ -32,6 +47,9 @@ public partial class ProviderDialog : ComponentBase [Inject] private IJSRuntime JsRuntime { get; set; } = null!; + /// + /// The list of used instance names. We need this to check for uniqueness. + /// private List usedInstanceNames { get; set; } = []; private bool dataIsValid; @@ -40,14 +58,17 @@ public partial class ProviderDialog : ComponentBase private string dataAPIKeyStorageIssue = string.Empty; private string dataEditingPreviousInstanceName = string.Empty; + // We get the form reference from Blazor code to validate it manually: private MudForm form = null!; #region Overrides of ComponentBase protected override async Task OnInitializedAsync() { + // Load the used instance names: this.usedInstanceNames = this.SettingsManager.ConfigurationData.Providers.Select(x => x.InstanceName.ToLowerInvariant()).ToList(); + // When editing, we need to load the data: if(this.IsEditing) { this.dataEditingPreviousInstanceName = this.DataInstanceName.ToLowerInvariant(); @@ -57,6 +78,7 @@ public partial class ProviderDialog : ComponentBase provider.InstanceName = this.DataInstanceName; + // Load the API key: var requestedSecret = await this.SettingsManager.GetAPIKey(this.JsRuntime, provider); if(requestedSecret.Success) this.dataAPIKey = requestedSecret.Secret; @@ -72,6 +94,8 @@ public partial class ProviderDialog : ComponentBase protected override async Task OnAfterRenderAsync(bool firstRender) { + // Reset the validation when not editing and on the first render. + // We don't want to show validation errors when the user opens the dialog. if(!this.IsEditing && firstRender) this.form.ResetValidation(); @@ -86,9 +110,12 @@ public partial class ProviderDialog : ComponentBase if (!string.IsNullOrWhiteSpace(this.dataAPIKeyStorageIssue)) this.dataAPIKeyStorageIssue = string.Empty; + // When the data is not valid, we don't store it: if (!this.dataIsValid) return; + // Use the data model to store the provider. + // We just return this data to the parent component: var addedProvider = new Provider { Id = this.DataId, @@ -96,9 +123,11 @@ public partial class ProviderDialog : ComponentBase UsedProvider = this.DataProvider, }; + // We need to instantiate the provider to store the API key: var provider = this.DataProvider.CreateProvider(); provider.InstanceName = this.DataInstanceName; + // Store the API key in the OS secure storage: var storeResponse = await this.SettingsManager.SetAPIKey(this.JsRuntime, provider, this.dataAPIKey); if (!storeResponse.Success) { diff --git a/app/MindWork AI Studio/Settings/SettingsManager.cs b/app/MindWork AI Studio/Settings/SettingsManager.cs index 3936e4c..af6adf5 100644 --- a/app/MindWork AI Studio/Settings/SettingsManager.cs +++ b/app/MindWork AI Studio/Settings/SettingsManager.cs @@ -6,14 +6,26 @@ using Microsoft.JSInterop; namespace AIStudio.Settings; +/// +/// The settings manager. +/// public sealed class SettingsManager { private const string SETTINGS_FILENAME = "settings.json"; + /// + /// The directory where the configuration files are stored. + /// public static string? ConfigDirectory { get; set; } + /// + /// The directory where the data files are stored. + /// public static string? DataDirectory { get; set; } + /// + /// The configuration data. + /// public Data ConfigurationData { get; private set; } = new(); private bool IsSetUp => !string.IsNullOrWhiteSpace(ConfigDirectory) && !string.IsNullOrWhiteSpace(DataDirectory); @@ -22,24 +34,62 @@ public sealed class SettingsManager private readonly record struct GetSecretRequest(string Destination, string UserName); + /// + /// Data structure for any requested secret. + /// + /// True, when the secret was successfully retrieved. + /// The secret, e.g., API key. + /// The issue, when the secret could not be retrieved. public readonly record struct RequestedSecret(bool Success, string Secret, string Issue); + /// + /// Try to get the API key for the given provider. + /// + /// The JS runtime to access the Rust code. + /// The provider to get the API key for. + /// The requested secret. public async Task GetAPIKey(IJSRuntime jsRuntime, IProvider provider) => await jsRuntime.InvokeAsync("window.__TAURI__.invoke", "get_secret", new GetSecretRequest($"provider::{provider.Id}::{provider.InstanceName}::api_key", Environment.UserName)); private readonly record struct StoreSecretRequest(string Destination, string UserName, string Secret); + /// + /// Data structure for storing a secret response. + /// + /// True, when the secret was successfully stored. + /// The issue, when the secret could not be stored. public readonly record struct StoreSecretResponse(bool Success, string Issue); + /// + /// Try to store the API key for the given provider. + /// + /// The JS runtime to access the Rust code. + /// The provider to store the API key for. + /// The API key to store. + /// The store secret response. public async Task SetAPIKey(IJSRuntime jsRuntime, IProvider provider, string key) => await jsRuntime.InvokeAsync("window.__TAURI__.invoke", "store_secret", new StoreSecretRequest($"provider::{provider.Id}::{provider.InstanceName}::api_key", Environment.UserName, key)); private readonly record struct DeleteSecretRequest(string Destination, string UserName); + /// + /// Data structure for deleting a secret response. + /// + /// True, when the secret was successfully deleted. + /// The issue, when the secret could not be deleted. public readonly record struct DeleteSecretResponse(bool Success, string Issue); + /// + /// Tries to delete the API key for the given provider. + /// + /// The JS runtime to access the Rust code. + /// The provider to delete the API key for. + /// The delete secret response. public async Task DeleteAPIKey(IJSRuntime jsRuntime, IProvider provider) => await jsRuntime.InvokeAsync("window.__TAURI__.invoke", "delete_secret", new DeleteSecretRequest($"provider::{provider.Id}::{provider.InstanceName}::api_key", Environment.UserName)); #endregion + /// + /// Loads the settings from the file system. + /// public async Task LoadSettings() { if(!this.IsSetUp) @@ -57,6 +107,9 @@ public sealed class SettingsManager this.ConfigurationData = loadedConfiguration; } + /// + /// Stores the settings to the file system. + /// public async Task StoreSettings() { if(!this.IsSetUp) diff --git a/app/MindWork AI Studio/Settings/Version.cs b/app/MindWork AI Studio/Settings/Version.cs index 0ce7765..047289e 100644 --- a/app/MindWork AI Studio/Settings/Version.cs +++ b/app/MindWork AI Studio/Settings/Version.cs @@ -1,5 +1,9 @@ namespace AIStudio.Settings; +/// +/// The version of the settings file. Allows us to upgrade the settings, +/// in case a new version is available. +/// public enum Version { UNKNOWN,