mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2025-02-05 14:29:07 +00:00
Added documentation
This commit is contained in:
parent
0859b1f2ec
commit
418f458118
@ -4,6 +4,9 @@ using MudBlazor;
|
||||
|
||||
namespace AIStudio.Components.CommonDialogs;
|
||||
|
||||
/// <summary>
|
||||
/// A confirmation dialog that can be used to ask the user for confirmation.
|
||||
/// </summary>
|
||||
public partial class ConfirmDialog : ComponentBase
|
||||
{
|
||||
[CascadingParameter]
|
||||
|
@ -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<string>("window.__TAURI__.path.appLocalDataDir");
|
||||
var configDir = await this.JsRuntime.InvokeAsync<string>("window.__TAURI__.path.appConfigDir");
|
||||
|
||||
// Store the directories in the settings manager:
|
||||
SettingsManager.ConfigDirectory = configDir;
|
||||
SettingsManager.DataDirectory = dataDir;
|
||||
|
||||
|
@ -2,6 +2,9 @@ using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Components.Pages;
|
||||
|
||||
/// <summary>
|
||||
/// The chat page.
|
||||
/// </summary>
|
||||
public partial class Chat : ComponentBase
|
||||
{
|
||||
private async Task SendMessage()
|
||||
|
@ -37,6 +37,8 @@ public partial class Settings : ComponentBase
|
||||
|
||||
#endregion
|
||||
|
||||
#region Provider related
|
||||
|
||||
private async Task AddProvider()
|
||||
{
|
||||
var dialogParameters = new DialogParameters<ProviderDialog>
|
||||
@ -96,4 +98,6 @@ public partial class Settings : ComponentBase
|
||||
await this.SettingsManager.StoreSettings();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
@ -3,10 +3,20 @@ using Microsoft.JSInterop;
|
||||
|
||||
namespace AIStudio.Provider;
|
||||
|
||||
/// <summary>
|
||||
/// A common interface for all providers.
|
||||
/// </summary>
|
||||
public interface IProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// The provider's ID.
|
||||
/// </summary>
|
||||
public string Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The provider's instance name. Useful for multiple instances of the same provider,
|
||||
/// e.g., to distinguish between different OpenAI API keys.
|
||||
/// </summary>
|
||||
public string InstanceName { get; set; }
|
||||
|
||||
public IAsyncEnumerable<string> GetChatCompletion(IJSRuntime jsRuntime, SettingsManager settings, Model chatModel, Thread chatThread);
|
||||
|
@ -2,14 +2,25 @@ using AIStudio.Provider.OpenAI;
|
||||
|
||||
namespace AIStudio.Provider;
|
||||
|
||||
/// <summary>
|
||||
/// Enum for all available providers.
|
||||
/// </summary>
|
||||
public enum Providers
|
||||
{
|
||||
NONE,
|
||||
OPEN_AI,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for the provider enum.
|
||||
/// </summary>
|
||||
public static class ExtensionsProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the human-readable name of the provider.
|
||||
/// </summary>
|
||||
/// <param name="provider">The provider.</param>
|
||||
/// <returns>The human-readable name of the provider.</returns>
|
||||
public static string ToName(this Providers provider) => provider switch
|
||||
{
|
||||
Providers.OPEN_AI => "OpenAI",
|
||||
@ -17,6 +28,11 @@ public static class ExtensionsProvider
|
||||
_ => "Unknown",
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new provider instance based on the provider value.
|
||||
/// </summary>
|
||||
/// <param name="provider">The provider value.</param>
|
||||
/// <returns>The provider instance.</returns>
|
||||
public static IProvider CreateProvider(this Providers provider) => provider switch
|
||||
{
|
||||
Providers.OPEN_AI => new ProviderOpenAI(),
|
||||
|
@ -1,8 +1,18 @@
|
||||
namespace AIStudio.Settings;
|
||||
|
||||
/// <summary>
|
||||
/// The data model for the settings file.
|
||||
/// </summary>
|
||||
public sealed class Data
|
||||
{
|
||||
public Version Version { get; init; }
|
||||
/// <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.V1;
|
||||
|
||||
/// <summary>
|
||||
/// List of configured providers.
|
||||
/// </summary>
|
||||
public List<Provider> Providers { get; init; } = new();
|
||||
}
|
@ -2,4 +2,25 @@ using AIStudio.Provider;
|
||||
|
||||
namespace AIStudio.Settings;
|
||||
|
||||
public readonly record struct Provider(string Id, string InstanceName, Providers UsedProvider);
|
||||
/// <summary>
|
||||
/// Data model for configured providers.
|
||||
/// </summary>
|
||||
/// <param name="Id">The provider's ID.</param>
|
||||
/// <param name="InstanceName">The provider's instance name. Useful for multiple instances of the same provider, e.g., to distinguish between different OpenAI API keys.</param>
|
||||
/// <param name="UsedProvider">The provider used.</param>
|
||||
public readonly record struct Provider(string Id, string InstanceName, Providers UsedProvider)
|
||||
{
|
||||
#region Overrides of ValueType
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string that represents the current provider in a human-readable format.
|
||||
/// We use this to display the provider in the chat UI.
|
||||
/// </summary>
|
||||
/// <returns>A string that represents the current provider in a human-readable format.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.InstanceName} ({this.UsedProvider.ToName()})";
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
@ -9,20 +9,35 @@ using MudBlazor;
|
||||
|
||||
namespace AIStudio.Settings;
|
||||
|
||||
/// <summary>
|
||||
/// The provider settings dialog.
|
||||
/// </summary>
|
||||
public partial class ProviderDialog : ComponentBase
|
||||
{
|
||||
[CascadingParameter]
|
||||
private MudDialogInstance MudDialog { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The provider's ID.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string DataId { get; set; } = Guid.NewGuid().ToString();
|
||||
|
||||
/// <summary>
|
||||
/// The user chosen instance name.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string DataInstanceName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The provider to use.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Providers DataProvider { get; set; } = Providers.NONE;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Should the dialog be in editing mode?
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool IsEditing { get; init; }
|
||||
|
||||
@ -32,6 +47,9 @@ public partial class ProviderDialog : ComponentBase
|
||||
[Inject]
|
||||
private IJSRuntime JsRuntime { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The list of used instance names. We need this to check for uniqueness.
|
||||
/// </summary>
|
||||
private List<string> 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)
|
||||
{
|
||||
|
@ -6,14 +6,26 @@ using Microsoft.JSInterop;
|
||||
|
||||
namespace AIStudio.Settings;
|
||||
|
||||
/// <summary>
|
||||
/// The settings manager.
|
||||
/// </summary>
|
||||
public sealed class SettingsManager
|
||||
{
|
||||
private const string SETTINGS_FILENAME = "settings.json";
|
||||
|
||||
/// <summary>
|
||||
/// The directory where the configuration files are stored.
|
||||
/// </summary>
|
||||
public static string? ConfigDirectory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The directory where the data files are stored.
|
||||
/// </summary>
|
||||
public static string? DataDirectory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The configuration data.
|
||||
/// </summary>
|
||||
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);
|
||||
|
||||
/// <summary>
|
||||
/// Data structure for any requested secret.
|
||||
/// </summary>
|
||||
/// <param name="Success">True, when the secret was successfully retrieved.</param>
|
||||
/// <param name="Secret">The secret, e.g., API key.</param>
|
||||
/// <param name="Issue">The issue, when the secret could not be retrieved.</param>
|
||||
public readonly record struct RequestedSecret(bool Success, string Secret, string Issue);
|
||||
|
||||
/// <summary>
|
||||
/// Try to get the API key for the given provider.
|
||||
/// </summary>
|
||||
/// <param name="jsRuntime">The JS runtime to access the Rust code.</param>
|
||||
/// <param name="provider">The provider to get the API key for.</param>
|
||||
/// <returns>The requested secret.</returns>
|
||||
public async Task<RequestedSecret> GetAPIKey(IJSRuntime jsRuntime, IProvider provider) => await jsRuntime.InvokeAsync<RequestedSecret>("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);
|
||||
|
||||
/// <summary>
|
||||
/// Data structure for storing a secret response.
|
||||
/// </summary>
|
||||
/// <param name="Success">True, when the secret was successfully stored.</param>
|
||||
/// <param name="Issue">The issue, when the secret could not be stored.</param>
|
||||
public readonly record struct StoreSecretResponse(bool Success, string Issue);
|
||||
|
||||
/// <summary>
|
||||
/// Try to store the API key for the given provider.
|
||||
/// </summary>
|
||||
/// <param name="jsRuntime">The JS runtime to access the Rust code.</param>
|
||||
/// <param name="provider">The provider to store the API key for.</param>
|
||||
/// <param name="key">The API key to store.</param>
|
||||
/// <returns>The store secret response.</returns>
|
||||
public async Task<StoreSecretResponse> SetAPIKey(IJSRuntime jsRuntime, IProvider provider, string key) => await jsRuntime.InvokeAsync<StoreSecretResponse>("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);
|
||||
|
||||
/// <summary>
|
||||
/// Data structure for deleting a secret response.
|
||||
/// </summary>
|
||||
/// <param name="Success">True, when the secret was successfully deleted.</param>
|
||||
/// <param name="Issue">The issue, when the secret could not be deleted.</param>
|
||||
public readonly record struct DeleteSecretResponse(bool Success, string Issue);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to delete the API key for the given provider.
|
||||
/// </summary>
|
||||
/// <param name="jsRuntime">The JS runtime to access the Rust code.</param>
|
||||
/// <param name="provider">The provider to delete the API key for.</param>
|
||||
/// <returns>The delete secret response.</returns>
|
||||
public async Task<DeleteSecretResponse> DeleteAPIKey(IJSRuntime jsRuntime, IProvider provider) => await jsRuntime.InvokeAsync<DeleteSecretResponse>("window.__TAURI__.invoke", "delete_secret", new DeleteSecretRequest($"provider::{provider.Id}::{provider.InstanceName}::api_key", Environment.UserName));
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Loads the settings from the file system.
|
||||
/// </summary>
|
||||
public async Task LoadSettings()
|
||||
{
|
||||
if(!this.IsSetUp)
|
||||
@ -57,6 +107,9 @@ public sealed class SettingsManager
|
||||
this.ConfigurationData = loadedConfiguration;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores the settings to the file system.
|
||||
/// </summary>
|
||||
public async Task StoreSettings()
|
||||
{
|
||||
if(!this.IsSetUp)
|
||||
|
@ -1,5 +1,9 @@
|
||||
namespace AIStudio.Settings;
|
||||
|
||||
/// <summary>
|
||||
/// The version of the settings file. Allows us to upgrade the settings,
|
||||
/// in case a new version is available.
|
||||
/// </summary>
|
||||
public enum Version
|
||||
{
|
||||
UNKNOWN,
|
||||
|
Loading…
Reference in New Issue
Block a user