Implemented settings for setting up self-hosted or local provider

This commit is contained in:
Thorsten Sommer 2024-07-03 14:28:51 +02:00
parent 496ae94c2d
commit 88e3e46334
Signed by: tsommer
GPG Key ID: 371BBA77A02C0108
6 changed files with 81 additions and 9 deletions

View File

@ -101,7 +101,7 @@ public partial class Chat : ComponentBase
// Use the selected provider to get the AI response. // Use the selected provider to get the AI response.
// By awaiting this line, we wait for the entire // By awaiting this line, we wait for the entire
// content to be streamed. // 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: // Disable the stream state:
this.isStreaming = false; this.isStreaming = false;

View File

@ -25,7 +25,12 @@
<MudTd>@context.Num</MudTd> <MudTd>@context.Num</MudTd>
<MudTd>@context.InstanceName</MudTd> <MudTd>@context.InstanceName</MudTd>
<MudTd>@context.UsedProvider</MudTd> <MudTd>@context.UsedProvider</MudTd>
<MudTd>@context.Model</MudTd> <MudTd>
@if(context.UsedProvider is not Providers.SELF_HOSTED)
@context.Model
else
@("as selected by provider")
</MudTd>
<MudTd Style="text-align: left;"> <MudTd Style="text-align: left;">
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.OpenInBrowser" Class="ma-2" Href="@this.GetProviderDashboardURL(context.UsedProvider)" Target="_blank" Disabled="@(context.UsedProvider is Providers.NONE or Providers.SELF_HOSTED)"> <MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.OpenInBrowser" Class="ma-2" Href="@this.GetProviderDashboardURL(context.UsedProvider)" Target="_blank" Disabled="@(context.UsedProvider is Providers.NONE or Providers.SELF_HOSTED)">
Open Dashboard Open Dashboard

View File

@ -50,6 +50,8 @@ public partial class Settings : ComponentBase
{ x => x.DataInstanceName, provider.InstanceName }, { x => x.DataInstanceName, provider.InstanceName },
{ x => x.DataProvider, provider.UsedProvider }, { x => x.DataProvider, provider.UsedProvider },
{ x => x.DataModel, provider.Model }, { x => x.DataModel, provider.Model },
{ x => x.DataHostname, provider.Hostname },
{ x => x.IsSelfHosted, provider.IsSelfHosted },
{ x => x.IsEditing, true }, { x => x.IsEditing, true },
}; };

View File

@ -1,6 +1,7 @@
using AIStudio.Provider.Anthropic; using AIStudio.Provider.Anthropic;
using AIStudio.Provider.Mistral; using AIStudio.Provider.Mistral;
using AIStudio.Provider.OpenAI; using AIStudio.Provider.OpenAI;
using AIStudio.Provider.SelfHosted;
namespace AIStudio.Provider; namespace AIStudio.Provider;
@ -10,9 +11,12 @@ namespace AIStudio.Provider;
public enum Providers public enum Providers
{ {
NONE, NONE,
OPEN_AI, OPEN_AI,
ANTHROPIC, ANTHROPIC,
MISTRAL, MISTRAL,
SELF_HOSTED,
} }
/// <summary> /// <summary>
@ -27,11 +31,14 @@ public static class ExtensionsProvider
/// <returns>The human-readable name of the provider.</returns> /// <returns>The human-readable name of the provider.</returns>
public static string ToName(this Providers provider) => provider switch public static string ToName(this Providers provider) => provider switch
{ {
Providers.NONE => "No provider selected",
Providers.OPEN_AI => "OpenAI", Providers.OPEN_AI => "OpenAI",
Providers.ANTHROPIC => "Anthropic", Providers.ANTHROPIC => "Anthropic",
Providers.MISTRAL => "Mistral", Providers.MISTRAL => "Mistral",
Providers.NONE => "No provider selected", Providers.SELF_HOSTED => "Self-hosted",
_ => "Unknown", _ => "Unknown",
}; };
@ -40,13 +47,16 @@ public static class ExtensionsProvider
/// </summary> /// </summary>
/// <param name="provider">The provider value.</param> /// <param name="provider">The provider value.</param>
/// <param name="instanceName">The used instance name.</param> /// <param name="instanceName">The used instance name.</param>
/// <param name="hostname">The hostname of the provider.</param>
/// <returns>The provider instance.</returns> /// <returns>The provider instance.</returns>
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.OPEN_AI => new ProviderOpenAI { InstanceName = instanceName },
Providers.ANTHROPIC => new ProviderAnthropic { InstanceName = instanceName }, Providers.ANTHROPIC => new ProviderAnthropic { InstanceName = instanceName },
Providers.MISTRAL => new ProviderMistral { InstanceName = instanceName }, Providers.MISTRAL => new ProviderMistral { InstanceName = instanceName },
Providers.SELF_HOSTED => new ProviderSelfHosted(hostname) { InstanceName = instanceName },
_ => new NoProvider(), _ => new NoProvider(),
}; };
} }

View File

@ -12,7 +12,7 @@
<MudSelectItem Value="@provider">@provider</MudSelectItem> <MudSelectItem Value="@provider">@provider</MudSelectItem>
} }
</MudSelect> </MudSelect>
<MudButton Disabled="@(this.DataProvider is Providers.NONE or Providers.SELF_HOSTED)" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.GetProviderCreationURL()" Target="_blank">Create account</MudButton> <MudButton Disabled="@this.IsSelfHostedOrNone" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.GetProviderCreationURL()" Target="_blank">Create account</MudButton>
</MudStack> </MudStack>
@* ReSharper disable once CSharpWarnings::CS8974 *@ @* ReSharper disable once CSharpWarnings::CS8974 *@
@ -20,6 +20,7 @@
T="string" T="string"
@bind-Text="@this.dataAPIKey" @bind-Text="@this.dataAPIKey"
Label="API Key" Label="API Key"
Disabled="@this.IsSelfHostedOrNone"
Class="mb-3" Class="mb-3"
Adornment="Adornment.Start" Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.VpnKey" AdornmentIcon="@Icons.Material.Filled.VpnKey"
@ -28,9 +29,21 @@
Validation="@this.ValidatingAPIKey" Validation="@this.ValidatingAPIKey"
/> />
<MudTextField
T="string"
@bind-Text="@this.DataHostname"
Label="Hostname"
Disabled="@this.IsCloudProvider"
Class="mb-3"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Dns"
AdornmentColor="Color.Info"
Validation="@this.ValidatingHostname"
/>
<MudStack Row="@true" AlignItems="AlignItems.Center"> <MudStack Row="@true" AlignItems="AlignItems.Center">
<MudButton Disabled="@(!this.CanLoadModels)" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="this.ReloadModels">Load</MudButton> <MudButton Disabled="@(!this.CanLoadModels)" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="this.ReloadModels">Load</MudButton>
<MudSelect @bind-Value="@this.DataModel" Label="Model" Class="mb-3" OpenIcon="@Icons.Material.Filled.FaceRetouchingNatural" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.ValidatingModel"> <MudSelect Disabled="@this.IsSelfHostedOrNone" @bind-Value="@this.DataModel" Label="Model" Class="mb-3" OpenIcon="@Icons.Material.Filled.FaceRetouchingNatural" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.ValidatingModel">
@foreach (var model in this.availableModels) @foreach (var model in this.availableModels)
{ {
<MudSelectItem Value="@model">@model</MudSelectItem> <MudSelectItem Value="@model">@model</MudSelectItem>

View File

@ -32,6 +32,18 @@ public partial class ProviderDialog : ComponentBase
[Parameter] [Parameter]
public string DataInstanceName { get; set; } = string.Empty; public string DataInstanceName { get; set; } = string.Empty;
/// <summary>
/// The chosen hostname for self-hosted providers.
/// </summary>
[Parameter]
public string DataHostname { get; set; } = string.Empty;
/// <summary>
/// Is this provider self-hosted?
/// </summary>
[Parameter]
public bool IsSelfHosted { get; set; }
/// <summary> /// <summary>
/// The provider to use. /// The provider to use.
/// </summary> /// </summary>
@ -99,7 +111,8 @@ public partial class ProviderDialog : ComponentBase
this.dataAPIKey = requestedSecret.Secret; this.dataAPIKey = requestedSecret.Secret;
// Now, we try to load the list of available models: // 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 else
{ {
@ -142,6 +155,8 @@ public partial class ProviderDialog : ComponentBase
InstanceName = this.DataInstanceName, InstanceName = this.DataInstanceName,
UsedProvider = this.DataProvider, UsedProvider = this.DataProvider,
Model = this.DataModel, Model = this.DataModel,
IsSelfHosted = this.DataProvider is Providers.SELF_HOSTED,
Hostname = this.DataHostname,
}; };
// We need to instantiate the provider to store the API key: // 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) private string? ValidatingModel(Model model)
{ {
if(this.DataProvider is Providers.SELF_HOSTED)
return null;
if (model == default) if (model == default)
return "Please select a model."; return "Please select a model.";
@ -206,6 +224,9 @@ public partial class ProviderDialog : ComponentBase
private string? ValidatingAPIKey(string apiKey) private string? ValidatingAPIKey(string apiKey)
{ {
if(this.DataProvider is Providers.SELF_HOSTED)
return null;
if(!string.IsNullOrWhiteSpace(this.dataAPIKeyStorageIssue)) if(!string.IsNullOrWhiteSpace(this.dataAPIKeyStorageIssue))
return this.dataAPIKeyStorageIssue; return this.dataAPIKeyStorageIssue;
@ -215,9 +236,24 @@ public partial class ProviderDialog : ComponentBase
return null; return null;
} }
private void Cancel() => this.MudDialog.Cancel(); private string? ValidatingHostname(string hostname)
{
if(this.DataProvider != Providers.SELF_HOSTED)
return null;
private bool CanLoadModels => !string.IsNullOrWhiteSpace(this.dataAPIKey) && this.DataProvider != Providers.NONE; 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://";
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() private async Task ReloadModels()
{ {
@ -234,6 +270,12 @@ public partial class ProviderDialog : ComponentBase
this.availableModels.AddRange(orderedModels); 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 private string GetProviderCreationURL() => this.DataProvider switch
{ {
Providers.OPEN_AI => "https://platform.openai.com/signup", Providers.OPEN_AI => "https://platform.openai.com/signup",