2024-04-19 19:25:44 +00:00
using AIStudio.Provider ;
2024-09-01 18:10:03 +00:00
using AIStudio.Settings ;
2024-04-19 19:25:44 +00:00
using Microsoft.AspNetCore.Components ;
2024-07-16 08:28:13 +00:00
using Host = AIStudio . Provider . SelfHosted . Host ;
2024-09-01 18:10:03 +00:00
using RustService = AIStudio . Tools . RustService ;
2024-07-16 08:28:13 +00:00
2024-09-01 18:10:03 +00:00
namespace AIStudio.Dialogs ;
2024-04-19 19:25:44 +00:00
2024-05-04 08:55:00 +00:00
/// <summary>
/// The provider settings dialog.
/// </summary>
2024-04-19 19:25:44 +00:00
public partial class ProviderDialog : ComponentBase
{
[CascadingParameter]
private MudDialogInstance MudDialog { get ; set ; } = null ! ;
2024-05-19 18:28:25 +00:00
/// <summary>
/// The provider's number in the list.
/// </summary>
[Parameter]
public uint DataNum { get ; set ; }
2024-04-19 21:27:38 +00:00
2024-05-04 08:55:00 +00:00
/// <summary>
/// The provider's ID.
/// </summary>
2024-04-19 21:27:38 +00:00
[Parameter]
2024-04-20 15:06:50 +00:00
public string DataId { get ; set ; } = Guid . NewGuid ( ) . ToString ( ) ;
2024-05-04 08:55:00 +00:00
/// <summary>
/// The user chosen instance name.
/// </summary>
2024-04-20 15:06:50 +00:00
[Parameter]
public string DataInstanceName { get ; set ; } = string . Empty ;
2024-07-03 18:31:04 +00:00
/// <summary>
/// The chosen hostname for self-hosted providers.
/// </summary>
[Parameter]
public string DataHostname { get ; set ; } = string . Empty ;
2024-07-16 08:28:13 +00:00
/// <summary>
/// The local host to use, e.g., llama.cpp.
/// </summary>
[Parameter]
public Host DataHost { get ; set ; } = Host . NONE ;
2024-07-03 18:31:04 +00:00
/// <summary>
/// Is this provider self-hosted?
/// </summary>
[Parameter]
public bool IsSelfHosted { get ; set ; }
2024-05-04 08:55:00 +00:00
/// <summary>
/// The provider to use.
/// </summary>
2024-04-20 15:06:50 +00:00
[Parameter]
2024-09-13 19:50:00 +00:00
public LLMProviders DataLLMProvider { get ; set ; } = LLMProviders . NONE ;
2024-05-04 08:55:00 +00:00
2024-05-19 14:12:07 +00:00
/// <summary>
/// The LLM model to use, e.g., GPT-4o.
/// </summary>
[Parameter]
public Model DataModel { get ; set ; }
2024-05-04 08:55:00 +00:00
/// <summary>
/// Should the dialog be in editing mode?
/// </summary>
2024-04-20 15:06:50 +00:00
[Parameter]
public bool IsEditing { get ; init ; }
[Inject]
2024-09-01 18:10:03 +00:00
private SettingsManager SettingsManager { get ; init ; } = null ! ;
2024-04-20 15:06:50 +00:00
[Inject]
2024-09-01 18:10:03 +00:00
private ILogger < ProviderDialog > Logger { get ; init ; } = null ! ;
[Inject]
private RustService RustService { get ; init ; } = null ! ;
2024-04-19 19:25:44 +00:00
2024-07-16 08:28:13 +00:00
private static readonly Dictionary < string , object? > SPELLCHECK_ATTRIBUTES = new ( ) ;
2024-06-01 18:00:17 +00:00
2024-05-04 08:55:00 +00:00
/// <summary>
/// The list of used instance names. We need this to check for uniqueness.
/// </summary>
2024-05-19 14:12:50 +00:00
private List < string > UsedInstanceNames { get ; set ; } = [ ] ;
2024-04-20 15:06:50 +00:00
2024-04-19 19:25:44 +00:00
private bool dataIsValid ;
private string [ ] dataIssues = [ ] ;
2024-04-20 15:06:50 +00:00
private string dataAPIKey = string . Empty ;
2024-07-25 13:29:44 +00:00
private string dataManuallyModel = string . Empty ;
2024-04-20 15:06:50 +00:00
private string dataAPIKeyStorageIssue = string . Empty ;
private string dataEditingPreviousInstanceName = string . Empty ;
2024-04-19 19:25:44 +00:00
2024-05-04 08:55:00 +00:00
// We get the form reference from Blazor code to validate it manually:
2024-04-19 19:25:44 +00:00
private MudForm form = null ! ;
2024-05-19 14:12:07 +00:00
private readonly List < Model > availableModels = new ( ) ;
2024-09-01 18:10:03 +00:00
private readonly Encryption encryption = Program . ENCRYPTION ;
private Settings . Provider CreateProviderSettings ( ) = > new ( )
2024-07-16 08:28:13 +00:00
{
Num = this . DataNum ,
Id = this . DataId ,
InstanceName = this . DataInstanceName ,
2024-09-13 19:50:00 +00:00
UsedLLMProvider = this . DataLLMProvider ,
2024-11-09 21:04:00 +00:00
Model = this . DataLLMProvider is LLMProviders . FIREWORKS ? new Model ( this . dataManuallyModel , null ) : this . DataModel ,
2024-09-13 19:50:00 +00:00
IsSelfHosted = this . DataLLMProvider is LLMProviders . SELF_HOSTED ,
2024-07-24 17:27:25 +00:00
Hostname = this . DataHostname . EndsWith ( '/' ) ? this . DataHostname [ . . ^ 1 ] : this . DataHostname ,
2024-07-16 08:28:13 +00:00
Host = this . DataHost ,
} ;
2024-04-19 19:25:44 +00:00
2024-04-20 15:06:50 +00:00
#region Overrides of ComponentBase
protected override async Task OnInitializedAsync ( )
{
2024-06-01 18:00:17 +00:00
// Configure the spellchecking for the instance name input:
2024-07-16 08:28:13 +00:00
this . SettingsManager . InjectSpellchecking ( SPELLCHECK_ATTRIBUTES ) ;
2024-06-01 18:00:17 +00:00
2024-05-04 08:55:00 +00:00
// Load the used instance names:
2024-05-19 14:12:50 +00:00
this . UsedInstanceNames = this . SettingsManager . ConfigurationData . Providers . Select ( x = > x . InstanceName . ToLowerInvariant ( ) ) . ToList ( ) ;
2024-04-20 15:06:50 +00:00
2024-05-04 08:55:00 +00:00
// When editing, we need to load the data:
2024-04-20 15:06:50 +00:00
if ( this . IsEditing )
{
this . dataEditingPreviousInstanceName = this . DataInstanceName . ToLowerInvariant ( ) ;
2024-07-16 08:28:13 +00:00
2024-09-08 19:01:51 +00:00
// When using Fireworks, we must copy the model name:
2024-09-13 19:50:00 +00:00
if ( this . DataLLMProvider is LLMProviders . FIREWORKS )
2024-09-08 19:01:51 +00:00
this . dataManuallyModel = this . DataModel . Id ;
2024-07-16 08:28:13 +00:00
//
// We cannot load the API key for self-hosted providers:
//
2024-10-07 11:26:25 +00:00
if ( this . DataLLMProvider is LLMProviders . SELF_HOSTED & & this . DataHost is not Host . OLLAMA )
2024-07-16 08:28:13 +00:00
{
await this . ReloadModels ( ) ;
await base . OnInitializedAsync ( ) ;
return ;
}
var loadedProviderSettings = this . CreateProviderSettings ( ) ;
2024-09-01 18:10:03 +00:00
var provider = loadedProviderSettings . CreateProvider ( this . Logger ) ;
2024-04-20 15:06:50 +00:00
if ( provider is NoProvider )
2024-07-16 08:28:13 +00:00
{
await base . OnInitializedAsync ( ) ;
2024-04-20 15:06:50 +00:00
return ;
2024-07-16 08:28:13 +00:00
}
2024-04-20 15:06:50 +00:00
2024-05-04 08:55:00 +00:00
// Load the API key:
2024-10-07 11:26:25 +00:00
var requestedSecret = await this . RustService . GetAPIKey ( provider , isTrying : this . DataLLMProvider is LLMProviders . SELF_HOSTED ) ;
2024-04-20 15:06:50 +00:00
if ( requestedSecret . Success )
2024-05-19 14:12:07 +00:00
{
2024-09-01 18:10:03 +00:00
this . dataAPIKey = await requestedSecret . Secret . Decrypt ( this . encryption ) ;
2024-05-19 14:12:07 +00:00
// Now, we try to load the list of available models:
2024-07-16 08:28:13 +00:00
await this . ReloadModels ( ) ;
2024-05-19 14:12:07 +00:00
}
2024-04-20 15:06:50 +00:00
else
{
2024-10-07 11:26:25 +00:00
this . dataAPIKey = string . Empty ;
if ( this . DataLLMProvider is not LLMProviders . SELF_HOSTED )
{
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." ;
await this . form . Validate ( ) ;
}
// We still try to load the models. Some local hosts don't need an API key:
await this . ReloadModels ( ) ;
2024-04-20 15:06:50 +00:00
}
}
await base . OnInitializedAsync ( ) ;
}
protected override async Task OnAfterRenderAsync ( bool firstRender )
{
2024-05-04 08:55:00 +00:00
// 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.
2024-04-20 15:06:50 +00:00
if ( ! this . IsEditing & & firstRender )
this . form . ResetValidation ( ) ;
await base . OnAfterRenderAsync ( firstRender ) ;
}
#endregion
private async Task Store ( )
2024-04-19 19:25:44 +00:00
{
await this . form . Validate ( ) ;
2024-04-20 15:06:50 +00:00
if ( ! string . IsNullOrWhiteSpace ( this . dataAPIKeyStorageIssue ) )
this . dataAPIKeyStorageIssue = string . Empty ;
2024-05-04 08:55:00 +00:00
// When the data is not valid, we don't store it:
2024-04-19 19:25:44 +00:00
if ( ! this . dataIsValid )
return ;
2024-05-04 08:55:00 +00:00
// Use the data model to store the provider.
// We just return this data to the parent component:
2024-07-16 08:28:13 +00:00
var addedProviderSettings = this . CreateProviderSettings ( ) ;
2024-10-07 11:26:25 +00:00
if ( ! string . IsNullOrWhiteSpace ( this . dataAPIKey ) )
2024-04-19 19:25:44 +00:00
{
2024-07-16 08:28:13 +00:00
// We need to instantiate the provider to store the API key:
2024-09-01 18:10:03 +00:00
var provider = addedProviderSettings . CreateProvider ( this . Logger ) ;
2024-04-20 15:06:50 +00:00
2024-07-16 08:28:13 +00:00
// Store the API key in the OS secure storage:
2024-09-01 18:10:03 +00:00
var storeResponse = await this . RustService . SetAPIKey ( provider , this . dataAPIKey ) ;
2024-07-16 08:28:13 +00:00
if ( ! storeResponse . Success )
{
this . dataAPIKeyStorageIssue = $"Failed to store the API key in the operating system. The message was: {storeResponse.Issue}. Please try again." ;
await this . form . Validate ( ) ;
return ;
}
2024-04-20 15:06:50 +00:00
}
2024-07-16 08:28:13 +00:00
this . MudDialog . Close ( DialogResult . Ok ( addedProviderSettings ) ) ;
2024-04-19 19:25:44 +00:00
}
2024-09-13 19:50:00 +00:00
private string? ValidatingProvider ( LLMProviders llmProvider )
2024-04-19 19:25:44 +00:00
{
2024-09-13 19:50:00 +00:00
if ( llmProvider = = LLMProviders . NONE )
2024-04-19 19:25:44 +00:00
return "Please select a provider." ;
return null ;
}
2024-04-19 21:27:38 +00:00
2024-07-16 08:28:13 +00:00
private string? ValidatingHost ( Host host )
{
2024-09-13 19:50:00 +00:00
if ( this . DataLLMProvider is not LLMProviders . SELF_HOSTED )
2024-07-16 08:28:13 +00:00
return null ;
if ( host = = Host . NONE )
return "Please select a host." ;
return null ;
}
2024-07-25 13:29:44 +00:00
private string? ValidateManuallyModel ( string manuallyModel )
{
2024-09-13 19:50:00 +00:00
if ( this . DataLLMProvider is LLMProviders . FIREWORKS & & string . IsNullOrWhiteSpace ( manuallyModel ) )
2024-07-25 13:29:44 +00:00
return "Please enter a model name." ;
return null ;
}
2024-05-19 14:12:07 +00:00
private string? ValidatingModel ( Model model )
{
2024-09-13 19:50:00 +00:00
if ( this . DataLLMProvider is LLMProviders . SELF_HOSTED & & this . DataHost = = Host . LLAMACPP )
2024-07-03 18:31:04 +00:00
return null ;
2024-05-19 14:12:07 +00:00
if ( model = = default )
return "Please select a model." ;
return null ;
}
2024-09-08 19:01:51 +00:00
2024-04-19 21:27:38 +00:00
private string? ValidatingInstanceName ( string instanceName )
{
2024-07-03 18:31:04 +00:00
if ( string . IsNullOrWhiteSpace ( instanceName ) )
2024-04-20 15:06:50 +00:00
return "Please enter an instance name." ;
2024-09-08 19:01:51 +00:00
if ( instanceName . Length > 40 )
return "The instance name must not exceed 40 characters." ;
2024-07-03 18:31:04 +00:00
2024-04-19 21:27:38 +00:00
// The instance name must be unique:
2024-04-20 15:06:50 +00:00
var lowerInstanceName = instanceName . ToLowerInvariant ( ) ;
2024-05-19 14:12:50 +00:00
if ( lowerInstanceName ! = this . dataEditingPreviousInstanceName & & this . UsedInstanceNames . Contains ( lowerInstanceName ) )
2024-04-19 21:27:38 +00:00
return "The instance name must be unique; the chosen name is already in use." ;
return null ;
}
2024-04-20 15:06:50 +00:00
private string? ValidatingAPIKey ( string apiKey )
{
2024-09-13 19:50:00 +00:00
if ( this . DataLLMProvider is LLMProviders . SELF_HOSTED )
2024-07-03 18:31:04 +00:00
return null ;
2024-04-20 15:06:50 +00:00
if ( ! string . IsNullOrWhiteSpace ( this . dataAPIKeyStorageIssue ) )
return this . dataAPIKeyStorageIssue ;
if ( string . IsNullOrWhiteSpace ( apiKey ) )
return "Please enter an API key." ;
return null ;
}
2024-04-19 19:25:44 +00:00
2024-07-03 18:31:04 +00:00
private string? ValidatingHostname ( string hostname )
{
2024-09-13 19:50:00 +00:00
if ( this . DataLLMProvider ! = LLMProviders . SELF_HOSTED )
2024-07-03 18:31:04 +00:00
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://" ;
if ( ! Uri . TryCreate ( hostname , UriKind . Absolute , out _ ) )
return "The hostname is not a valid HTTP(S) URL." ;
return null ;
}
2024-05-19 14:12:07 +00:00
2024-07-03 18:31:04 +00:00
private void Cancel ( ) = > this . MudDialog . Cancel ( ) ;
2024-05-19 17:42:15 +00:00
2024-05-19 14:12:07 +00:00
private async Task ReloadModels ( )
{
2024-07-16 08:28:13 +00:00
var currentProviderSettings = this . CreateProviderSettings ( ) ;
2024-09-01 18:10:03 +00:00
var provider = currentProviderSettings . CreateProvider ( this . Logger ) ;
2024-05-19 14:12:07 +00:00
if ( provider is NoProvider )
return ;
2024-07-24 17:27:25 +00:00
2024-09-01 18:10:03 +00:00
var models = await provider . GetTextModels ( this . dataAPIKey ) ;
2024-05-19 14:12:07 +00:00
// 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 ) ;
}
2024-07-03 18:31:04 +00:00
2024-07-16 08:28:13 +00:00
private bool CanLoadModels ( )
{
2024-09-13 19:50:00 +00:00
if ( this . DataLLMProvider is LLMProviders . SELF_HOSTED )
2024-07-16 08:28:13 +00:00
{
switch ( this . DataHost )
{
case Host . NONE :
return false ;
case Host . LLAMACPP :
return false ;
case Host . LM_STUDIO :
return true ;
case Host . OLLAMA :
return true ;
default :
return false ;
}
}
2024-09-13 19:50:00 +00:00
if ( this . DataLLMProvider is LLMProviders . NONE )
2024-07-16 08:28:13 +00:00
return false ;
if ( string . IsNullOrWhiteSpace ( this . dataAPIKey ) )
return false ;
return true ;
}
2024-09-13 19:50:00 +00:00
private bool ShowRegisterButton = > this . DataLLMProvider switch
2024-07-25 13:29:44 +00:00
{
2024-09-13 19:50:00 +00:00
LLMProviders . OPEN_AI = > true ,
LLMProviders . MISTRAL = > true ,
LLMProviders . ANTHROPIC = > true ,
2024-11-09 21:04:00 +00:00
LLMProviders . GOOGLE = > true ,
2024-07-25 13:29:44 +00:00
2024-11-09 19:13:14 +00:00
LLMProviders . GROQ = > true ,
2024-09-13 19:50:00 +00:00
LLMProviders . FIREWORKS = > true ,
2024-07-25 13:29:44 +00:00
_ = > false ,
} ;
2024-09-13 19:50:00 +00:00
private bool NeedAPIKey = > this . DataLLMProvider switch
2024-07-25 13:29:44 +00:00
{
2024-09-13 19:50:00 +00:00
LLMProviders . OPEN_AI = > true ,
LLMProviders . MISTRAL = > true ,
LLMProviders . ANTHROPIC = > true ,
2024-11-09 21:04:00 +00:00
LLMProviders . GOOGLE = > true ,
2024-07-25 13:29:44 +00:00
2024-11-09 19:13:14 +00:00
LLMProviders . GROQ = > true ,
2024-09-13 19:50:00 +00:00
LLMProviders . FIREWORKS = > true ,
2024-11-09 19:13:14 +00:00
2024-10-07 11:26:25 +00:00
LLMProviders . SELF_HOSTED = > this . DataHost is Host . OLLAMA ,
2024-07-25 13:29:44 +00:00
_ = > false ,
} ;
2024-10-07 11:26:25 +00:00
private string APIKeyText = > this . DataLLMProvider switch
{
LLMProviders . SELF_HOSTED = > "(Optional) API Key" ,
_ = > "API Key" ,
} ;
2024-07-25 13:29:44 +00:00
2024-09-13 19:50:00 +00:00
private bool NeedHostname = > this . DataLLMProvider switch
2024-07-25 13:29:44 +00:00
{
2024-09-13 19:50:00 +00:00
LLMProviders . SELF_HOSTED = > true ,
2024-07-25 13:29:44 +00:00
_ = > false ,
} ;
2024-09-13 19:50:00 +00:00
private bool NeedHost = > this . DataLLMProvider switch
2024-07-25 13:29:44 +00:00
{
2024-09-13 19:50:00 +00:00
LLMProviders . SELF_HOSTED = > true ,
2024-07-25 13:29:44 +00:00
_ = > false ,
} ;
2024-07-03 18:31:04 +00:00
2024-09-13 19:50:00 +00:00
private bool ProvideModelManually = > this . DataLLMProvider switch
2024-07-25 13:29:44 +00:00
{
2024-09-13 19:50:00 +00:00
LLMProviders . FIREWORKS = > true ,
2024-07-25 13:29:44 +00:00
_ = > false ,
} ;
2024-07-16 08:28:13 +00:00
2024-09-13 19:50:00 +00:00
private string GetModelOverviewURL ( ) = > this . DataLLMProvider switch
2024-07-25 13:29:44 +00:00
{
2024-09-13 19:50:00 +00:00
LLMProviders . FIREWORKS = > "https://fireworks.ai/models?show=Serverless" ,
2024-07-25 13:29:44 +00:00
_ = > string . Empty ,
} ;
2024-07-03 18:31:04 +00:00
2024-09-13 19:50:00 +00:00
private string GetProviderCreationURL ( ) = > this . DataLLMProvider switch
2024-07-03 18:31:04 +00:00
{
2024-09-13 19:50:00 +00:00
LLMProviders . OPEN_AI = > "https://platform.openai.com/signup" ,
LLMProviders . MISTRAL = > "https://console.mistral.ai/" ,
LLMProviders . ANTHROPIC = > "https://console.anthropic.com/dashboard" ,
2024-11-09 21:04:00 +00:00
LLMProviders . GOOGLE = > "https://console.cloud.google.com/" ,
2024-11-09 19:13:14 +00:00
LLMProviders . GROQ = > "https://console.groq.com/" ,
2024-09-13 19:50:00 +00:00
LLMProviders . FIREWORKS = > "https://fireworks.ai/login" ,
2024-07-25 13:29:44 +00:00
2024-07-03 18:31:04 +00:00
_ = > string . Empty ,
} ;
2024-07-25 13:29:44 +00:00
2024-09-13 19:50:00 +00:00
private bool IsNoneProvider = > this . DataLLMProvider is LLMProviders . NONE ;
2024-04-19 19:25:44 +00:00
}