From 88e3e46334b2a7d81080267657bb3ecf2f629c60 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Wed, 3 Jul 2024 14:28:51 +0200 Subject: [PATCH] Implemented settings for setting up self-hosted or local provider --- .../Components/Pages/Chat.razor.cs | 2 +- .../Components/Pages/Settings.razor | 7 ++- .../Components/Pages/Settings.razor.cs | 2 + app/MindWork AI Studio/Provider/Providers.cs | 14 +++++- .../Settings/ProviderDialog.razor | 17 ++++++- .../Settings/ProviderDialog.razor.cs | 48 +++++++++++++++++-- 6 files changed, 81 insertions(+), 9 deletions(-) diff --git a/app/MindWork AI Studio/Components/Pages/Chat.razor.cs b/app/MindWork AI Studio/Components/Pages/Chat.razor.cs index 53c22aa0..08275bd1 100644 --- a/app/MindWork AI Studio/Components/Pages/Chat.razor.cs +++ b/app/MindWork AI Studio/Components/Pages/Chat.razor.cs @@ -101,7 +101,7 @@ public partial class Chat : ComponentBase // Use the selected provider to get the AI response. // By awaiting this line, we wait for the entire // content to be streamed. - await aiText.CreateFromProviderAsync(this.selectedProvider.UsedProvider.CreateProvider(this.selectedProvider.InstanceName), this.JsRuntime, this.SettingsManager, this.selectedProvider.Model, this.chatThread); + await aiText.CreateFromProviderAsync(this.selectedProvider.UsedProvider.CreateProvider(this.selectedProvider.InstanceName, this.selectedProvider.Hostname), this.JsRuntime, this.SettingsManager, this.selectedProvider.Model, this.chatThread); // Disable the stream state: this.isStreaming = false; diff --git a/app/MindWork AI Studio/Components/Pages/Settings.razor b/app/MindWork AI Studio/Components/Pages/Settings.razor index cff1db0b..2a9414e4 100644 --- a/app/MindWork AI Studio/Components/Pages/Settings.razor +++ b/app/MindWork AI Studio/Components/Pages/Settings.razor @@ -25,7 +25,12 @@ @context.Num @context.InstanceName @context.UsedProvider - @context.Model + + @if(context.UsedProvider is not Providers.SELF_HOSTED) + @context.Model + else + @("as selected by provider") + Open Dashboard diff --git a/app/MindWork AI Studio/Components/Pages/Settings.razor.cs b/app/MindWork AI Studio/Components/Pages/Settings.razor.cs index d1b32517..e6df7480 100644 --- a/app/MindWork AI Studio/Components/Pages/Settings.razor.cs +++ b/app/MindWork AI Studio/Components/Pages/Settings.razor.cs @@ -50,6 +50,8 @@ public partial class Settings : ComponentBase { x => x.DataInstanceName, provider.InstanceName }, { x => x.DataProvider, provider.UsedProvider }, { x => x.DataModel, provider.Model }, + { x => x.DataHostname, provider.Hostname }, + { x => x.IsSelfHosted, provider.IsSelfHosted }, { x => x.IsEditing, true }, }; diff --git a/app/MindWork AI Studio/Provider/Providers.cs b/app/MindWork AI Studio/Provider/Providers.cs index 6c8326f2..db99cc3e 100644 --- a/app/MindWork AI Studio/Provider/Providers.cs +++ b/app/MindWork AI Studio/Provider/Providers.cs @@ -1,6 +1,7 @@ using AIStudio.Provider.Anthropic; using AIStudio.Provider.Mistral; using AIStudio.Provider.OpenAI; +using AIStudio.Provider.SelfHosted; namespace AIStudio.Provider; @@ -10,9 +11,12 @@ namespace AIStudio.Provider; public enum Providers { NONE, + OPEN_AI, ANTHROPIC, MISTRAL, + + SELF_HOSTED, } /// @@ -27,11 +31,14 @@ public static class ExtensionsProvider /// The human-readable name of the provider. public static string ToName(this Providers provider) => provider switch { + Providers.NONE => "No provider selected", + Providers.OPEN_AI => "OpenAI", Providers.ANTHROPIC => "Anthropic", Providers.MISTRAL => "Mistral", - Providers.NONE => "No provider selected", + Providers.SELF_HOSTED => "Self-hosted", + _ => "Unknown", }; @@ -40,13 +47,16 @@ public static class ExtensionsProvider /// /// The provider value. /// The used instance name. + /// The hostname of the provider. /// The provider instance. - public static IProvider CreateProvider(this Providers provider, string instanceName) => provider switch + public static IProvider CreateProvider(this Providers provider, string instanceName, string hostname = "http://localhost:1234") => provider switch { Providers.OPEN_AI => new ProviderOpenAI { InstanceName = instanceName }, Providers.ANTHROPIC => new ProviderAnthropic { InstanceName = instanceName }, Providers.MISTRAL => new ProviderMistral { InstanceName = instanceName }, + Providers.SELF_HOSTED => new ProviderSelfHosted(hostname) { InstanceName = instanceName }, + _ => new NoProvider(), }; } \ No newline at end of file diff --git a/app/MindWork AI Studio/Settings/ProviderDialog.razor b/app/MindWork AI Studio/Settings/ProviderDialog.razor index 0a58a9e2..82bb5fc1 100644 --- a/app/MindWork AI Studio/Settings/ProviderDialog.razor +++ b/app/MindWork AI Studio/Settings/ProviderDialog.razor @@ -12,7 +12,7 @@ @provider } - Create account + Create account @* ReSharper disable once CSharpWarnings::CS8974 *@ @@ -20,6 +20,7 @@ T="string" @bind-Text="@this.dataAPIKey" Label="API Key" + Disabled="@this.IsSelfHostedOrNone" Class="mb-3" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.VpnKey" @@ -27,10 +28,22 @@ InputType="InputType.Password" Validation="@this.ValidatingAPIKey" /> + + Load - + @foreach (var model in this.availableModels) { @model diff --git a/app/MindWork AI Studio/Settings/ProviderDialog.razor.cs b/app/MindWork AI Studio/Settings/ProviderDialog.razor.cs index 8e391348..b752cc04 100644 --- a/app/MindWork AI Studio/Settings/ProviderDialog.razor.cs +++ b/app/MindWork AI Studio/Settings/ProviderDialog.razor.cs @@ -32,6 +32,18 @@ public partial class ProviderDialog : ComponentBase [Parameter] public string DataInstanceName { get; set; } = string.Empty; + /// + /// The chosen hostname for self-hosted providers. + /// + [Parameter] + public string DataHostname { get; set; } = string.Empty; + + /// + /// Is this provider self-hosted? + /// + [Parameter] + public bool IsSelfHosted { get; set; } + /// /// The provider to use. /// @@ -99,7 +111,8 @@ public partial class ProviderDialog : ComponentBase this.dataAPIKey = requestedSecret.Secret; // Now, we try to load the list of available models: - await this.ReloadModels(); + if(this.DataProvider is not Providers.SELF_HOSTED) + await this.ReloadModels(); } else { @@ -142,6 +155,8 @@ public partial class ProviderDialog : ComponentBase InstanceName = this.DataInstanceName, UsedProvider = this.DataProvider, Model = this.DataModel, + IsSelfHosted = this.DataProvider is Providers.SELF_HOSTED, + Hostname = this.DataHostname, }; // We need to instantiate the provider to store the API key: @@ -169,6 +184,9 @@ public partial class ProviderDialog : ComponentBase private string? ValidatingModel(Model model) { + if(this.DataProvider is Providers.SELF_HOSTED) + return null; + if (model == default) return "Please select a model."; @@ -206,6 +224,9 @@ public partial class ProviderDialog : ComponentBase private string? ValidatingAPIKey(string apiKey) { + if(this.DataProvider is Providers.SELF_HOSTED) + return null; + if(!string.IsNullOrWhiteSpace(this.dataAPIKeyStorageIssue)) return this.dataAPIKeyStorageIssue; @@ -215,9 +236,24 @@ public partial class ProviderDialog : ComponentBase return null; } - private void Cancel() => this.MudDialog.Cancel(); + private string? ValidatingHostname(string hostname) + { + if(this.DataProvider != Providers.SELF_HOSTED) + return null; + + if(string.IsNullOrWhiteSpace(hostname)) + return "Please enter a hostname, e.g., http://localhost:1234"; + + if(!hostname.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase) && !hostname.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase)) + return "The hostname must start with either http:// or https://"; - private bool CanLoadModels => !string.IsNullOrWhiteSpace(this.dataAPIKey) && this.DataProvider != Providers.NONE; + if(!Uri.TryCreate(hostname, UriKind.Absolute, out _)) + return "The hostname is not a valid HTTP(S) URL."; + + return null; + } + + private void Cancel() => this.MudDialog.Cancel(); private async Task ReloadModels() { @@ -233,6 +269,12 @@ public partial class ProviderDialog : ComponentBase this.availableModels.Clear(); this.availableModels.AddRange(orderedModels); } + + private bool CanLoadModels => !string.IsNullOrWhiteSpace(this.dataAPIKey) && this.DataProvider != Providers.NONE && this.DataProvider != Providers.SELF_HOSTED; + + private bool IsCloudProvider => this.DataProvider is not Providers.SELF_HOSTED or Providers.NONE; + + private bool IsSelfHostedOrNone => this.DataProvider is Providers.SELF_HOSTED or Providers.NONE; private string GetProviderCreationURL() => this.DataProvider switch {