mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2025-02-11 10:09:06 +00:00
Merge branch '34-choosing-a-model' into 'main'
Resolve "Choosing a model" Closes #34 See merge request products/mindwork-ai-studio!4
This commit is contained in:
commit
329b05e84a
@ -102,7 +102,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.JsRuntime, this.SettingsManager, new Model("gpt-4o"), this.chatThread);
|
await aiText.CreateFromProviderAsync(this.selectedProvider.UsedProvider.CreateProvider(this.selectedProvider.InstanceName), this.JsRuntime, this.SettingsManager, this.selectedProvider.Model, this.chatThread);
|
||||||
|
|
||||||
// Disable the stream state:
|
// Disable the stream state:
|
||||||
this.isStreaming = false;
|
this.isStreaming = false;
|
||||||
|
@ -7,20 +7,23 @@
|
|||||||
<MudTable Items="@this.SettingsManager.ConfigurationData.Providers">
|
<MudTable Items="@this.SettingsManager.ConfigurationData.Providers">
|
||||||
<ColGroup>
|
<ColGroup>
|
||||||
<col style="width: 3em;"/>
|
<col style="width: 3em;"/>
|
||||||
<col style="width: 6em;"/>
|
<col style="width: 12em;"/>
|
||||||
|
<col style="width: 12em;"/>
|
||||||
<col/>
|
<col/>
|
||||||
<col style="width: 20em;"/>
|
<col style="width: 20em;"/>
|
||||||
</ColGroup>
|
</ColGroup>
|
||||||
<HeaderContent>
|
<HeaderContent>
|
||||||
<MudTh>#</MudTh>
|
<MudTh>#</MudTh>
|
||||||
|
<MudTh>Instance Name</MudTh>
|
||||||
<MudTh>Provider</MudTh>
|
<MudTh>Provider</MudTh>
|
||||||
<MudTh>Name</MudTh>
|
<MudTh>Model</MudTh>
|
||||||
<MudTh Style="text-align: left;">Actions</MudTh>
|
<MudTh Style="text-align: left;">Actions</MudTh>
|
||||||
</HeaderContent>
|
</HeaderContent>
|
||||||
<RowTemplate>
|
<RowTemplate>
|
||||||
<MudTd></MudTd>
|
<MudTd>@context.Num</MudTd>
|
||||||
<MudTd>@context.UsedProvider</MudTd>
|
|
||||||
<MudTd>@context.InstanceName</MudTd>
|
<MudTd>@context.InstanceName</MudTd>
|
||||||
|
<MudTd>@context.UsedProvider</MudTd>
|
||||||
|
<MudTd>@context.Model</MudTd>
|
||||||
<MudTd Style="text-align: left;">
|
<MudTd Style="text-align: left;">
|
||||||
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Edit" Class="mr-2" OnClick="() => this.EditProvider(context)">
|
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Edit" Class="mr-2" OnClick="() => this.EditProvider(context)">
|
||||||
Edit
|
Edit
|
||||||
|
@ -52,6 +52,8 @@ public partial class Settings : ComponentBase
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
var addedProvider = (AIStudio.Settings.Provider)dialogResult.Data;
|
var addedProvider = (AIStudio.Settings.Provider)dialogResult.Data;
|
||||||
|
addedProvider = addedProvider with { Num = this.SettingsManager.ConfigurationData.NextProviderNum++ };
|
||||||
|
|
||||||
this.SettingsManager.ConfigurationData.Providers.Add(addedProvider);
|
this.SettingsManager.ConfigurationData.Providers.Add(addedProvider);
|
||||||
await this.SettingsManager.StoreSettings();
|
await this.SettingsManager.StoreSettings();
|
||||||
}
|
}
|
||||||
@ -60,9 +62,11 @@ public partial class Settings : ComponentBase
|
|||||||
{
|
{
|
||||||
var dialogParameters = new DialogParameters<ProviderDialog>
|
var dialogParameters = new DialogParameters<ProviderDialog>
|
||||||
{
|
{
|
||||||
|
{ x => x.DataNum, provider.Num },
|
||||||
{ x => x.DataId, provider.Id },
|
{ x => x.DataId, provider.Id },
|
||||||
{ 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.IsEditing, true },
|
{ x => x.IsEditing, true },
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -72,6 +76,12 @@ public partial class Settings : ComponentBase
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
var editedProvider = (AIStudio.Settings.Provider)dialogResult.Data;
|
var editedProvider = (AIStudio.Settings.Provider)dialogResult.Data;
|
||||||
|
|
||||||
|
// Set the provider number if it's not set. This is important for providers
|
||||||
|
// added before we started saving the provider number.
|
||||||
|
if(editedProvider.Num == 0)
|
||||||
|
editedProvider = editedProvider with { Num = this.SettingsManager.ConfigurationData.NextProviderNum++ };
|
||||||
|
|
||||||
this.SettingsManager.ConfigurationData.Providers[this.SettingsManager.ConfigurationData.Providers.IndexOf(provider)] = editedProvider;
|
this.SettingsManager.ConfigurationData.Providers[this.SettingsManager.ConfigurationData.Providers.IndexOf(provider)] = editedProvider;
|
||||||
await this.SettingsManager.StoreSettings();
|
await this.SettingsManager.StoreSettings();
|
||||||
}
|
}
|
||||||
@ -88,9 +98,7 @@ public partial class Settings : ComponentBase
|
|||||||
if (dialogResult.Canceled)
|
if (dialogResult.Canceled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var providerInstance = provider.UsedProvider.CreateProvider();
|
var providerInstance = provider.UsedProvider.CreateProvider(provider.InstanceName);
|
||||||
providerInstance.InstanceName = provider.InstanceName;
|
|
||||||
|
|
||||||
var deleteSecretResponse = await this.SettingsManager.DeleteAPIKey(this.JsRuntime, providerInstance);
|
var deleteSecretResponse = await this.SettingsManager.DeleteAPIKey(this.JsRuntime, providerInstance);
|
||||||
if(deleteSecretResponse.Success)
|
if(deleteSecretResponse.Success)
|
||||||
{
|
{
|
||||||
|
@ -20,12 +20,18 @@
|
|||||||
<EnableUnsafeUTF7Encoding>false</EnableUnsafeUTF7Encoding> <!-- Remove unsafe UTF7 encoding -->
|
<EnableUnsafeUTF7Encoding>false</EnableUnsafeUTF7Encoding> <!-- Remove unsafe UTF7 encoding -->
|
||||||
<JsonSerializerIsReflectionEnabledByDefault>true</JsonSerializerIsReflectionEnabledByDefault> <!-- Enable reflection for JSON serialization -->
|
<JsonSerializerIsReflectionEnabledByDefault>true</JsonSerializerIsReflectionEnabledByDefault> <!-- Enable reflection for JSON serialization -->
|
||||||
<SuppressTrimAnalysisWarnings>true</SuppressTrimAnalysisWarnings> <!-- Suppress trim analysis warnings -->
|
<SuppressTrimAnalysisWarnings>true</SuppressTrimAnalysisWarnings> <!-- Suppress trim analysis warnings -->
|
||||||
<NoWarn>IL2026</NoWarn> <!-- Suppress warning IL2026: Usage of methods marked as RequiresUnreferencedCode. None issue here, since we use trim mode partial, though. -->
|
|
||||||
|
<!--
|
||||||
|
IL2026: Usage of methods marked as RequiresUnreferencedCode. None issue here, since we use partial trim mode, though.
|
||||||
|
CS8974: Converting method group to non-delegate type; Did you intend to invoke the method? We have this issue with MudBlazor validation methods.
|
||||||
|
-->
|
||||||
|
<NoWarn>IL2026, CS8974</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<!-- Remove launchSettings.json from the project -->
|
<!-- Remove launchSettings.json from the project -->
|
||||||
<None Remove="Properties\launchSettings.json" />
|
<None Remove="Properties\launchSettings.json" />
|
||||||
|
<None Remove="build.nu" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
using System.Reflection;
|
|
||||||
|
|
||||||
using AIStudio;
|
using AIStudio;
|
||||||
using AIStudio.Components;
|
using AIStudio.Components;
|
||||||
using AIStudio.Settings;
|
using AIStudio.Settings;
|
||||||
using AIStudio.Tools;
|
using AIStudio.Tools;
|
||||||
|
|
||||||
using Microsoft.Extensions.FileProviders;
|
|
||||||
|
|
||||||
using MudBlazor;
|
using MudBlazor;
|
||||||
using MudBlazor.Services;
|
using MudBlazor.Services;
|
||||||
|
|
||||||
|
#if !DEBUG
|
||||||
|
using System.Reflection;
|
||||||
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
#endif
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder();
|
var builder = WebApplication.CreateBuilder();
|
||||||
builder.Services.AddMudServices(config =>
|
builder.Services.AddMudServices(config =>
|
||||||
{
|
{
|
||||||
|
@ -53,7 +53,7 @@ public interface IProvider
|
|||||||
/// <param name="settings">The settings manager to access the API key.</param>
|
/// <param name="settings">The settings manager to access the API key.</param>
|
||||||
/// <param name="token">The cancellation token.</param>
|
/// <param name="token">The cancellation token.</param>
|
||||||
/// <returns>The list of text models.</returns>
|
/// <returns>The list of text models.</returns>
|
||||||
public Task<IList<Model>> GetTextModels(IJSRuntime jsRuntime, SettingsManager settings, CancellationToken token = default);
|
public Task<IEnumerable<Model>> GetTextModels(IJSRuntime jsRuntime, SettingsManager settings, CancellationToken token = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Load all possible image models that can be used with this provider.
|
/// Load all possible image models that can be used with this provider.
|
||||||
@ -62,5 +62,5 @@ public interface IProvider
|
|||||||
/// <param name="settings">The settings manager to access the API key.</param>
|
/// <param name="settings">The settings manager to access the API key.</param>
|
||||||
/// <param name="token">The cancellation token.</param>
|
/// <param name="token">The cancellation token.</param>
|
||||||
/// <returns>The list of image models.</returns>
|
/// <returns>The list of image models.</returns>
|
||||||
public Task<IList<Model>> GetImageModels(IJSRuntime jsRuntime, SettingsManager settings, CancellationToken token = default);
|
public Task<IEnumerable<Model>> GetImageModels(IJSRuntime jsRuntime, SettingsManager settings, CancellationToken token = default);
|
||||||
}
|
}
|
@ -3,5 +3,5 @@ namespace AIStudio.Provider;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// An image URL.
|
/// An image URL.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="url">The image URL.</param>
|
/// <param name="URL">The image URL.</param>
|
||||||
public readonly record struct ImageURL(string url);
|
public readonly record struct ImageURL(string URL);
|
@ -4,4 +4,11 @@ namespace AIStudio.Provider;
|
|||||||
/// The data model for the model to use.
|
/// The data model for the model to use.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="Id">The model's ID.</param>
|
/// <param name="Id">The model's ID.</param>
|
||||||
public readonly record struct Model(string Id);
|
public readonly record struct Model(string Id)
|
||||||
|
{
|
||||||
|
#region Overrides of ValueType
|
||||||
|
|
||||||
|
public override string ToString() => string.IsNullOrWhiteSpace(this.Id) ? "no model selected" : this.Id;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
@ -17,9 +17,9 @@ public class NoProvider : IProvider
|
|||||||
|
|
||||||
public string InstanceName { get; set; } = "None";
|
public string InstanceName { get; set; } = "None";
|
||||||
|
|
||||||
public Task<IList<Model>> GetTextModels(IJSRuntime jsRuntime, SettingsManager settings, CancellationToken token = default) => Task.FromResult<IList<Model>>(new List<Model>());
|
public Task<IEnumerable<Model>> GetTextModels(IJSRuntime jsRuntime, SettingsManager settings, CancellationToken token = default) => Task.FromResult<IEnumerable<Model>>([]);
|
||||||
|
|
||||||
public Task<IList<Model>> GetImageModels(IJSRuntime jsRuntime, SettingsManager settings, CancellationToken token = default) => Task.FromResult<IList<Model>>(new List<Model>());
|
public Task<IEnumerable<Model>> GetImageModels(IJSRuntime jsRuntime, SettingsManager settings, CancellationToken token = default) => Task.FromResult<IEnumerable<Model>>([]);
|
||||||
|
|
||||||
public async IAsyncEnumerable<string> StreamChatCompletion(IJSRuntime jsRuntime, SettingsManager settings, Model chatModel, ChatThread chatChatThread, [EnumeratorCancellation] CancellationToken token = default)
|
public async IAsyncEnumerable<string> StreamChatCompletion(IJSRuntime jsRuntime, SettingsManager settings, Model chatModel, ChatThread chatChatThread, [EnumeratorCancellation] CancellationToken token = default)
|
||||||
{
|
{
|
||||||
|
@ -155,34 +155,33 @@ public sealed class ProviderOpenAI() : BaseProvider("https://api.openai.com/v1/"
|
|||||||
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<IList<Model>> GetTextModels(IJSRuntime jsRuntime, SettingsManager settings, CancellationToken token = default)
|
public Task<IEnumerable<Model>> GetTextModels(IJSRuntime jsRuntime, SettingsManager settings, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
return await this.LoadModels(jsRuntime, settings, "gpt-", token);
|
return this.LoadModels(jsRuntime, settings, "gpt-", token);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<IList<Model>> GetImageModels(IJSRuntime jsRuntime, SettingsManager settings, CancellationToken token = default)
|
public Task<IEnumerable<Model>> GetImageModels(IJSRuntime jsRuntime, SettingsManager settings, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
return await this.LoadModels(jsRuntime, settings, "dall-e-", token);
|
return this.LoadModels(jsRuntime, settings, "dall-e-", token);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private async Task<IList<Model>> LoadModels(IJSRuntime jsRuntime, SettingsManager settings, string prefix, CancellationToken token)
|
private async Task<IEnumerable<Model>> LoadModels(IJSRuntime jsRuntime, SettingsManager settings, string prefix, CancellationToken token)
|
||||||
{
|
{
|
||||||
var requestedSecret = await settings.GetAPIKey(jsRuntime, this);
|
var requestedSecret = await settings.GetAPIKey(jsRuntime, this);
|
||||||
if(!requestedSecret.Success)
|
if (!requestedSecret.Success)
|
||||||
return new List<Model>();
|
return [];
|
||||||
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Get, "models");
|
var request = new HttpRequestMessage(HttpMethod.Get, "models");
|
||||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", requestedSecret.Secret);
|
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", requestedSecret.Secret);
|
||||||
|
|
||||||
var emptyList = new List<Model>();
|
|
||||||
var response = await this.httpClient.SendAsync(request, token);
|
var response = await this.httpClient.SendAsync(request, token);
|
||||||
if(!response.IsSuccessStatusCode)
|
if(!response.IsSuccessStatusCode)
|
||||||
return emptyList;
|
return [];
|
||||||
|
|
||||||
var modelResponse = await response.Content.ReadFromJsonAsync<ModelsResponse>(token);
|
var modelResponse = await response.Content.ReadFromJsonAsync<ModelsResponse>(token);
|
||||||
return modelResponse.Data.Where(n => n.Id.StartsWith(prefix, StringComparison.InvariantCulture)).ToList();
|
return modelResponse.Data.Where(n => n.Id.StartsWith(prefix, StringComparison.InvariantCulture));
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -25,6 +25,7 @@ public static class ExtensionsProvider
|
|||||||
{
|
{
|
||||||
Providers.OPEN_AI => "OpenAI",
|
Providers.OPEN_AI => "OpenAI",
|
||||||
|
|
||||||
|
Providers.NONE => "No provider selected",
|
||||||
_ => "Unknown",
|
_ => "Unknown",
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -32,10 +33,11 @@ public static class ExtensionsProvider
|
|||||||
/// Creates a new provider instance based on the provider value.
|
/// Creates a new provider instance based on the provider value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="provider">The provider value.</param>
|
/// <param name="provider">The provider value.</param>
|
||||||
|
/// <param name="instanceName">The used instance name.</param>
|
||||||
/// <returns>The provider instance.</returns>
|
/// <returns>The provider instance.</returns>
|
||||||
public static IProvider CreateProvider(this Providers provider) => provider switch
|
public static IProvider CreateProvider(this Providers provider, string instanceName) => provider switch
|
||||||
{
|
{
|
||||||
Providers.OPEN_AI => new ProviderOpenAI(),
|
Providers.OPEN_AI => new ProviderOpenAI { InstanceName = instanceName },
|
||||||
|
|
||||||
_ => new NoProvider(),
|
_ => new NoProvider(),
|
||||||
};
|
};
|
||||||
|
@ -14,7 +14,12 @@ public sealed class Data
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// List of configured providers.
|
/// List of configured providers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<Provider> Providers { get; init; } = new();
|
public List<Provider> Providers { get; init; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The next provider number to use.
|
||||||
|
/// </summary>
|
||||||
|
public uint NextProviderNum { get; set; } = 1;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Should we save energy? When true, we will update content streamed
|
/// Should we save energy? When true, we will update content streamed
|
||||||
|
@ -5,10 +5,12 @@ namespace AIStudio.Settings;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Data model for configured providers.
|
/// Data model for configured providers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="Num">The provider's number.</param>
|
||||||
/// <param name="Id">The provider's ID.</param>
|
/// <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="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>
|
/// <param name="UsedProvider">The provider used.</param>
|
||||||
public readonly record struct Provider(string Id, string InstanceName, Providers UsedProvider)
|
/// <param name="Model">The LLM model to use for chat.</param>
|
||||||
|
public readonly record struct Provider(uint Num, string Id, string InstanceName, Providers UsedProvider, Model Model)
|
||||||
{
|
{
|
||||||
#region Overrides of ValueType
|
#region Overrides of ValueType
|
||||||
|
|
||||||
@ -19,7 +21,7 @@ public readonly record struct Provider(string Id, string InstanceName, Providers
|
|||||||
/// <returns>A string that represents the current provider in a human-readable format.</returns>
|
/// <returns>A string that represents the current provider in a human-readable format.</returns>
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"{this.InstanceName} ({this.UsedProvider.ToName()})";
|
return $"{this.InstanceName} ({this.UsedProvider.ToName()}, {this.Model})";
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
T="string"
|
T="string"
|
||||||
@bind-Text="@this.DataInstanceName"
|
@bind-Text="@this.DataInstanceName"
|
||||||
Label="Instance Name"
|
Label="Instance Name"
|
||||||
|
Class="mb-3"
|
||||||
Adornment="Adornment.Start"
|
Adornment="Adornment.Start"
|
||||||
AdornmentIcon="@Icons.Material.Filled.Lightbulb"
|
AdornmentIcon="@Icons.Material.Filled.Lightbulb"
|
||||||
AdornmentColor="Color.Info"
|
AdornmentColor="Color.Info"
|
||||||
@ -16,7 +17,7 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
@* ReSharper disable once CSharpWarnings::CS8974 *@
|
@* ReSharper disable once CSharpWarnings::CS8974 *@
|
||||||
<MudSelect @bind-Value="@this.DataProvider" Label="Provider" OpenIcon="@Icons.Material.Filled.AccountBalance" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.ValidatingProvider">
|
<MudSelect @bind-Value="@this.DataProvider" Label="Provider" Class="mb-3" OpenIcon="@Icons.Material.Filled.AccountBalance" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.ValidatingProvider">
|
||||||
@foreach (Providers provider in Enum.GetValues(typeof(Providers)))
|
@foreach (Providers provider in Enum.GetValues(typeof(Providers)))
|
||||||
{
|
{
|
||||||
<MudSelectItem Value="@provider">@provider</MudSelectItem>
|
<MudSelectItem Value="@provider">@provider</MudSelectItem>
|
||||||
@ -28,12 +29,24 @@
|
|||||||
T="string"
|
T="string"
|
||||||
@bind-Text="@this.dataAPIKey"
|
@bind-Text="@this.dataAPIKey"
|
||||||
Label="API Key"
|
Label="API Key"
|
||||||
|
Class="mb-3"
|
||||||
Adornment="Adornment.Start"
|
Adornment="Adornment.Start"
|
||||||
AdornmentIcon="@Icons.Material.Filled.VpnKey"
|
AdornmentIcon="@Icons.Material.Filled.VpnKey"
|
||||||
AdornmentColor="Color.Info"
|
AdornmentColor="Color.Info"
|
||||||
InputType="InputType.Password"
|
InputType="InputType.Password"
|
||||||
Validation="@this.ValidatingAPIKey"
|
Validation="@this.ValidatingAPIKey"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<MudStack Row="@true" AlignItems="AlignItems.Center">
|
||||||
|
<MudButton Disabled="@(!this.CanLoadModels)" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="this.ReloadModels">Reload</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">
|
||||||
|
@foreach (var model in this.availableModels)
|
||||||
|
{
|
||||||
|
<MudSelectItem Value="@model">@model</MudSelectItem>
|
||||||
|
}
|
||||||
|
</MudSelect>
|
||||||
|
</MudStack>
|
||||||
|
|
||||||
</MudForm>
|
</MudForm>
|
||||||
|
|
||||||
@if (this.dataIssues.Any())
|
@if (this.dataIssues.Any())
|
||||||
|
@ -17,6 +17,12 @@ public partial class ProviderDialog : ComponentBase
|
|||||||
[CascadingParameter]
|
[CascadingParameter]
|
||||||
private MudDialogInstance MudDialog { get; set; } = null!;
|
private MudDialogInstance MudDialog { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The provider's number in the list.
|
||||||
|
/// </summary>
|
||||||
|
[Parameter]
|
||||||
|
public uint DataNum { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The provider's ID.
|
/// The provider's ID.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -35,6 +41,12 @@ public partial class ProviderDialog : ComponentBase
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public Providers DataProvider { get; set; } = Providers.NONE;
|
public Providers DataProvider { get; set; } = Providers.NONE;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The LLM model to use, e.g., GPT-4o.
|
||||||
|
/// </summary>
|
||||||
|
[Parameter]
|
||||||
|
public Model DataModel { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Should the dialog be in editing mode?
|
/// Should the dialog be in editing mode?
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -50,7 +62,7 @@ public partial class ProviderDialog : ComponentBase
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The list of used instance names. We need this to check for uniqueness.
|
/// The list of used instance names. We need this to check for uniqueness.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private List<string> usedInstanceNames { get; set; } = [];
|
private List<string> UsedInstanceNames { get; set; } = [];
|
||||||
|
|
||||||
private bool dataIsValid;
|
private bool dataIsValid;
|
||||||
private string[] dataIssues = [];
|
private string[] dataIssues = [];
|
||||||
@ -61,27 +73,32 @@ public partial class ProviderDialog : ComponentBase
|
|||||||
// We get the form reference from Blazor code to validate it manually:
|
// We get the form reference from Blazor code to validate it manually:
|
||||||
private MudForm form = null!;
|
private MudForm form = null!;
|
||||||
|
|
||||||
|
private readonly List<Model> availableModels = new();
|
||||||
|
|
||||||
#region Overrides of ComponentBase
|
#region Overrides of ComponentBase
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
// Load the used instance names:
|
// Load the used instance names:
|
||||||
this.usedInstanceNames = this.SettingsManager.ConfigurationData.Providers.Select(x => x.InstanceName.ToLowerInvariant()).ToList();
|
this.UsedInstanceNames = this.SettingsManager.ConfigurationData.Providers.Select(x => x.InstanceName.ToLowerInvariant()).ToList();
|
||||||
|
|
||||||
// When editing, we need to load the data:
|
// When editing, we need to load the data:
|
||||||
if(this.IsEditing)
|
if(this.IsEditing)
|
||||||
{
|
{
|
||||||
this.dataEditingPreviousInstanceName = this.DataInstanceName.ToLowerInvariant();
|
this.dataEditingPreviousInstanceName = this.DataInstanceName.ToLowerInvariant();
|
||||||
var provider = this.DataProvider.CreateProvider();
|
var provider = this.DataProvider.CreateProvider(this.DataInstanceName);
|
||||||
if(provider is NoProvider)
|
if(provider is NoProvider)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
provider.InstanceName = this.DataInstanceName;
|
|
||||||
|
|
||||||
// Load the API key:
|
// Load the API key:
|
||||||
var requestedSecret = await this.SettingsManager.GetAPIKey(this.JsRuntime, provider);
|
var requestedSecret = await this.SettingsManager.GetAPIKey(this.JsRuntime, provider);
|
||||||
if(requestedSecret.Success)
|
if(requestedSecret.Success)
|
||||||
|
{
|
||||||
this.dataAPIKey = requestedSecret.Secret;
|
this.dataAPIKey = requestedSecret.Secret;
|
||||||
|
|
||||||
|
// Now, we try to load the list of available models:
|
||||||
|
await this.ReloadModels();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.dataAPIKeyStorageIssue = $"Failed to load the API key from the operating system. The message was: {requestedSecret.Issue}. You might ignore this message and provide the API key again.";
|
this.dataAPIKeyStorageIssue = $"Failed to load the API key from the operating system. The message was: {requestedSecret.Issue}. You might ignore this message and provide the API key again.";
|
||||||
@ -118,14 +135,15 @@ public partial class ProviderDialog : ComponentBase
|
|||||||
// We just return this data to the parent component:
|
// We just return this data to the parent component:
|
||||||
var addedProvider = new Provider
|
var addedProvider = new Provider
|
||||||
{
|
{
|
||||||
|
Num = this.DataNum,
|
||||||
Id = this.DataId,
|
Id = this.DataId,
|
||||||
InstanceName = this.DataInstanceName,
|
InstanceName = this.DataInstanceName,
|
||||||
UsedProvider = this.DataProvider,
|
UsedProvider = this.DataProvider,
|
||||||
|
Model = this.DataModel,
|
||||||
};
|
};
|
||||||
|
|
||||||
// We need to instantiate the provider to store the API key:
|
// We need to instantiate the provider to store the API key:
|
||||||
var provider = this.DataProvider.CreateProvider();
|
var provider = this.DataProvider.CreateProvider(this.DataInstanceName);
|
||||||
provider.InstanceName = this.DataInstanceName;
|
|
||||||
|
|
||||||
// Store the API key in the OS secure storage:
|
// Store the API key in the OS secure storage:
|
||||||
var storeResponse = await this.SettingsManager.SetAPIKey(this.JsRuntime, provider, this.dataAPIKey);
|
var storeResponse = await this.SettingsManager.SetAPIKey(this.JsRuntime, provider, this.dataAPIKey);
|
||||||
@ -147,6 +165,14 @@ public partial class ProviderDialog : ComponentBase
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string? ValidatingModel(Model model)
|
||||||
|
{
|
||||||
|
if (model == default)
|
||||||
|
return "Please select a model.";
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
[GeneratedRegex("^[a-zA-Z0-9 ]+$")]
|
[GeneratedRegex("^[a-zA-Z0-9 ]+$")]
|
||||||
private static partial Regex InstanceNameRegex();
|
private static partial Regex InstanceNameRegex();
|
||||||
|
|
||||||
@ -170,7 +196,7 @@ public partial class ProviderDialog : ComponentBase
|
|||||||
|
|
||||||
// The instance name must be unique:
|
// The instance name must be unique:
|
||||||
var lowerInstanceName = instanceName.ToLowerInvariant();
|
var lowerInstanceName = instanceName.ToLowerInvariant();
|
||||||
if (lowerInstanceName != this.dataEditingPreviousInstanceName && this.usedInstanceNames.Contains(lowerInstanceName))
|
if (lowerInstanceName != this.dataEditingPreviousInstanceName && this.UsedInstanceNames.Contains(lowerInstanceName))
|
||||||
return "The instance name must be unique; the chosen name is already in use.";
|
return "The instance name must be unique; the chosen name is already in use.";
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@ -188,4 +214,21 @@ public partial class ProviderDialog : ComponentBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void Cancel() => this.MudDialog.Cancel();
|
private void Cancel() => this.MudDialog.Cancel();
|
||||||
|
|
||||||
|
private bool CanLoadModels => !string.IsNullOrWhiteSpace(this.dataAPIKey) && this.DataProvider != Providers.NONE && !string.IsNullOrWhiteSpace(this.DataInstanceName);
|
||||||
|
|
||||||
|
private async Task ReloadModels()
|
||||||
|
{
|
||||||
|
var provider = this.DataProvider.CreateProvider(this.DataInstanceName);
|
||||||
|
if(provider is NoProvider)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var models = await provider.GetTextModels(this.JsRuntime, this.SettingsManager);
|
||||||
|
|
||||||
|
// Order descending by ID means that the newest models probably come first:
|
||||||
|
var orderedModels = models.OrderByDescending(n => n.Id);
|
||||||
|
|
||||||
|
this.availableModels.Clear();
|
||||||
|
this.availableModels.AddRange(orderedModels);
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user