mirror of
				https://github.com/MindWorkAI/AI-Studio.git
				synced 2025-10-31 13:00:20 +00:00 
			
		
		
		
	Added embedding configurations (#224)
This commit is contained in:
		
							parent
							
								
									f806ee28c5
								
							
						
					
					
						commit
						799112eb9d
					
				| @ -9,10 +9,10 @@ Things we are currently working on: | ||||
|   - [x] ~~Define the [External Data API (EDI)](https://github.com/MindWorkAI/EDI) as a contract for integrating arbitrary external data (PR [#1](https://github.com/MindWorkAI/EDI/pull/1))~~ | ||||
|   - [x] ~~App: Metadata for providers (which provider offers embeddings?) (PR [#205](https://github.com/MindWorkAI/AI-Studio/pull/205))~~ | ||||
|   - [x] ~~App: Add an option to show preview features (PR [#222](https://github.com/MindWorkAI/AI-Studio/pull/222))~~ | ||||
|   - [ ] App: Configure embedding providers | ||||
|   - [ ] ~~App: Configure embedding providers (PR [#224](https://github.com/MindWorkAI/AI-Studio/pull/224))~~ | ||||
|   - [ ] App: Management of data sources (local & external data via [EDI](https://github.com/MindWorkAI/EDI)) | ||||
|   - [ ] Runtime: Extract data from txt / md / pdf / docx / xlsx files | ||||
|   - [ ] Runtime: Implement internal embedding provider through [fastembed-rs](https://github.com/Anush008/fastembed-rs) | ||||
|   - [ ] (*Optional*) Runtime: Implement internal embedding provider through [fastembed-rs](https://github.com/Anush008/fastembed-rs) | ||||
|   - [ ] App: Implement external embedding providers | ||||
|   - [ ] App: Implement the process to vectorize one local file using embeddings | ||||
|   - [ ] Runtime: Integration of the vector database [LanceDB](https://github.com/lancedb/lancedb) | ||||
|  | ||||
							
								
								
									
										20
									
								
								app/MindWork AI Studio/Components/PreviewAlpha.razor
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								app/MindWork AI Studio/Components/PreviewAlpha.razor
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | ||||
| <MudTooltip Placement="Placement.Bottom" Arrow="@true"> | ||||
|     <ChildContent> | ||||
|         <MudChip T="string" Icon="@Icons.Material.Filled.FirstPage" Color="Color.Error" Class="mb-3"> | ||||
|             Alpha | ||||
|         </MudChip> | ||||
|     </ChildContent> | ||||
|     <TooltipContent> | ||||
|         <div style="max-width: 22em;"> | ||||
|             <MudText Typo="Typo.body2" Class="mb-3"> | ||||
|                 This feature is currently in the alpha phase. | ||||
|                 Expect bugs and unfinished work. | ||||
|             </MudText> | ||||
| 
 | ||||
|             <MudText Typo="Typo.body2"> | ||||
|                 Alpha phase means that we are working on the | ||||
|                 last details before the beta phase. | ||||
|             </MudText> | ||||
|         </div> | ||||
|     </TooltipContent> | ||||
| </MudTooltip> | ||||
							
								
								
									
										5
									
								
								app/MindWork AI Studio/Components/PreviewAlpha.razor.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								app/MindWork AI Studio/Components/PreviewAlpha.razor.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| using Microsoft.AspNetCore.Components; | ||||
| 
 | ||||
| namespace AIStudio.Components; | ||||
| 
 | ||||
| public partial class PreviewAlpha : ComponentBase; | ||||
							
								
								
									
										19
									
								
								app/MindWork AI Studio/Components/PreviewBeta.razor
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								app/MindWork AI Studio/Components/PreviewBeta.razor
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | ||||
| <MudTooltip Placement="Placement.Bottom" Arrow="@true"> | ||||
|     <ChildContent> | ||||
|         <MudChip T="string" Icon="@Icons.Material.Filled.HourglassTop" Color="Color.Info" Class="mb-3"> | ||||
|             Beta | ||||
|         </MudChip> | ||||
|     </ChildContent> | ||||
|     <TooltipContent> | ||||
|         <div style="max-width: 20em;"> | ||||
|             <MudText Typo="Typo.body2" Class="mb-3"> | ||||
|                 This feature is currently in the beta phase. | ||||
|                 It is still be possible that there are some bugs. | ||||
|             </MudText> | ||||
| 
 | ||||
|             <MudText Typo="Typo.body2"> | ||||
|                 Beta phase means that we are testing the feature. | ||||
|             </MudText> | ||||
|         </div> | ||||
|     </TooltipContent> | ||||
| </MudTooltip> | ||||
							
								
								
									
										5
									
								
								app/MindWork AI Studio/Components/PreviewBeta.razor.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								app/MindWork AI Studio/Components/PreviewBeta.razor.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| using Microsoft.AspNetCore.Components; | ||||
| 
 | ||||
| namespace AIStudio.Components; | ||||
| 
 | ||||
| public partial class PreviewBeta : ComponentBase; | ||||
							
								
								
									
										22
									
								
								app/MindWork AI Studio/Components/PreviewExperimental.razor
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								app/MindWork AI Studio/Components/PreviewExperimental.razor
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | ||||
| <MudTooltip Placement="Placement.Bottom" Arrow="@true"> | ||||
|     <ChildContent> | ||||
|         <MudChip T="string" Icon="@Icons.Material.Filled.Science" Color="Color.Error" Class="mb-3"> | ||||
|             Experimental | ||||
|         </MudChip> | ||||
|     </ChildContent> | ||||
|     <TooltipContent> | ||||
|         <div style="max-width: 26em;"> | ||||
|             <MudText Typo="Typo.body2" Class="mb-3"> | ||||
|                 This feature is currently in the experimental phase. | ||||
|                 Expect bugs, unfinished work, changes in future | ||||
|                 versions, and more. | ||||
|             </MudText> | ||||
| 
 | ||||
|             <MudText Typo="Typo.body2"> | ||||
|                 Experimental phase means that we have a vision for a feature | ||||
|                 but not a clear plan yet. We are still exploring the  | ||||
|                 possibilities. | ||||
|             </MudText> | ||||
|         </div> | ||||
|     </TooltipContent> | ||||
| </MudTooltip> | ||||
| @ -0,0 +1,5 @@ | ||||
| using Microsoft.AspNetCore.Components; | ||||
| 
 | ||||
| namespace AIStudio.Components; | ||||
| 
 | ||||
| public partial class PreviewExperimental : ComponentBase; | ||||
							
								
								
									
										21
									
								
								app/MindWork AI Studio/Components/PreviewPrototype.razor
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								app/MindWork AI Studio/Components/PreviewPrototype.razor
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | ||||
| <MudTooltip Placement="Placement.Bottom" Arrow="@true"> | ||||
|     <ChildContent> | ||||
|         <MudChip T="string" Icon="@Icons.Material.Filled.HourglassBottom" Color="Color.Error" Class="mb-3"> | ||||
|             Prototype | ||||
|         </MudChip> | ||||
|     </ChildContent> | ||||
|     <TooltipContent> | ||||
|         <div style="max-width: 22em;"> | ||||
|             <MudText Typo="Typo.body2" Class="mb-3"> | ||||
|                 This feature is currently in the prototype phase. | ||||
|                 Expect bugs, unfinished work, changes in future | ||||
|                 versions, and more. | ||||
|             </MudText> | ||||
| 
 | ||||
|             <MudText Typo="Typo.body2"> | ||||
|                 Prototype phase means that we have a plan but we | ||||
|                 are still working on it. | ||||
|             </MudText> | ||||
|         </div> | ||||
|     </TooltipContent> | ||||
| </MudTooltip> | ||||
| @ -0,0 +1,5 @@ | ||||
| using Microsoft.AspNetCore.Components; | ||||
| 
 | ||||
| namespace AIStudio.Components; | ||||
| 
 | ||||
| public partial class PreviewPrototype : ComponentBase; | ||||
| @ -0,0 +1,19 @@ | ||||
| <MudTooltip Placement="Placement.Bottom" Arrow="@true"> | ||||
|     <ChildContent> | ||||
|         <MudChip T="string" Icon="@Icons.Material.Filled.VerifiedUser" Color="Color.Success" Class="mb-3"> | ||||
|             Release Candidate | ||||
|         </MudChip> | ||||
|     </ChildContent> | ||||
|     <TooltipContent> | ||||
|         <div style="max-width: 20em;"> | ||||
|             <MudText Typo="Typo.body2" Class="mb-3"> | ||||
|                 This feature is about to be released. We think it's ready for production. | ||||
|                 There should be no more bugs. | ||||
|             </MudText> | ||||
| 
 | ||||
|             <MudText Typo="Typo.body2"> | ||||
|                 Release candidates are the final step before a feature is proven to be stable. | ||||
|             </MudText> | ||||
|         </div> | ||||
|     </TooltipContent> | ||||
| </MudTooltip> | ||||
| @ -0,0 +1,5 @@ | ||||
| using Microsoft.AspNetCore.Components; | ||||
| 
 | ||||
| namespace AIStudio.Components; | ||||
| 
 | ||||
| public partial class PreviewReleaseCandidate : ComponentBase; | ||||
							
								
								
									
										118
									
								
								app/MindWork AI Studio/Dialogs/EmbeddingDialog.razor
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								app/MindWork AI Studio/Dialogs/EmbeddingDialog.razor
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,118 @@ | ||||
| @using AIStudio.Provider | ||||
| @using AIStudio.Provider.SelfHosted | ||||
| 
 | ||||
| <MudDialog> | ||||
|     <DialogContent> | ||||
|         <MudForm @ref="@this.form" @bind-IsValid="@this.dataIsValid" @bind-Errors="@this.dataIssues"> | ||||
|             <MudStack Row="@true" AlignItems="AlignItems.Center"> | ||||
|                 @* ReSharper disable once CSharpWarnings::CS8974 *@ | ||||
|                 <MudSelect @bind-Value="@this.DataLLMProvider" Label="Provider" Class="mb-3" OpenIcon="@Icons.Material.Filled.AccountBalance" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingProvider"> | ||||
|                     @foreach (LLMProviders provider in Enum.GetValues(typeof(LLMProviders))) | ||||
|                     { | ||||
|                         if (provider.ProvideEmbeddings()) | ||||
|                         { | ||||
|                             <MudSelectItem Value="@provider">@provider</MudSelectItem> | ||||
|                         } | ||||
|                     } | ||||
|                 </MudSelect> | ||||
|                 <MudButton Disabled="@(!this.DataLLMProvider.ShowRegisterButton())" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.DataLLMProvider.GetCreationURL()" Target="_blank">Create account</MudButton> | ||||
|             </MudStack> | ||||
|              | ||||
|             @* ReSharper disable once CSharpWarnings::CS8974 *@ | ||||
|             <MudTextField | ||||
|                 T="string" | ||||
|                 @bind-Text="@this.dataAPIKey" | ||||
|                 Label="@this.APIKeyText" | ||||
|                 Disabled="@(!this.DataLLMProvider.IsAPIKeyNeeded(this.DataHost))" | ||||
|                 Class="mb-3" | ||||
|                 Adornment="Adornment.Start" | ||||
|                 AdornmentIcon="@Icons.Material.Filled.VpnKey" | ||||
|                 AdornmentColor="Color.Info" | ||||
|                 InputType="InputType.Password" | ||||
|                 Validation="@this.providerValidation.ValidatingAPIKey" | ||||
|             /> | ||||
|              | ||||
|             <MudTextField | ||||
|                 T="string" | ||||
|                 @bind-Text="@this.DataHostname" | ||||
|                 Label="Hostname" | ||||
|                 Disabled="@(!this.DataLLMProvider.IsHostnameNeeded())" | ||||
|                 Class="mb-3" | ||||
|                 Adornment="Adornment.Start" | ||||
|                 AdornmentIcon="@Icons.Material.Filled.Dns" | ||||
|                 AdornmentColor="Color.Info" | ||||
|                 Validation="@this.providerValidation.ValidatingHostname" | ||||
|                 UserAttributes="@SPELLCHECK_ATTRIBUTES" | ||||
|             /> | ||||
| 
 | ||||
|             <MudSelect Disabled="@(!this.DataLLMProvider.IsHostNeeded())" @bind-Value="@this.DataHost" Label="Host" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingHost"> | ||||
|                 @foreach (Host host in Enum.GetValues(typeof(Host))) | ||||
|                 { | ||||
|                     if (host.AreEmbeddingsSupported()) | ||||
|                     { | ||||
|                         <MudSelectItem Value="@host">@host.Name()</MudSelectItem> | ||||
|                     } | ||||
|                 } | ||||
|             </MudSelect> | ||||
| 
 | ||||
|             <MudStack Row="@true" AlignItems="AlignItems.Center"> | ||||
|                 @if (this.DataLLMProvider.IsEmbeddingModelProvidedManually(this.DataHost)) | ||||
|                 { | ||||
|                     <MudTextField | ||||
|                         T="string" | ||||
|                         @bind-Text="@this.dataManuallyModel" | ||||
|                         Label="Model" | ||||
|                         Class="mb-3" | ||||
|                         Adornment="Adornment.Start" | ||||
|                         AdornmentIcon="@Icons.Material.Filled.Dns" | ||||
|                         AdornmentColor="Color.Info" | ||||
|                         Validation="@this.ValidateManuallyModel" | ||||
|                         UserAttributes="@SPELLCHECK_ATTRIBUTES" | ||||
|                         HelperText="Currently, we cannot query the embedding models of self-hosted systems. Therefore, enter the model name manually." | ||||
|                     /> | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     <MudButton Disabled="@(!this.DataLLMProvider.CanLoadModels(this.DataHost, this.dataAPIKey))" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="this.ReloadModels">Load</MudButton> | ||||
|                     <MudSelect Disabled="@this.IsNoneProvider" @bind-Value="@this.DataModel" Label="Model" Class="mb-3" OpenIcon="@Icons.Material.Filled.FaceRetouchingNatural" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingModel"> | ||||
|                         @foreach (var model in this.availableModels) | ||||
|                         { | ||||
|                             <MudSelectItem Value="@model">@model</MudSelectItem> | ||||
|                         } | ||||
|                     </MudSelect> | ||||
|                 } | ||||
|             </MudStack> | ||||
| 
 | ||||
|             @* ReSharper disable once CSharpWarnings::CS8974 *@ | ||||
|             <MudTextField | ||||
|                 T="string" | ||||
|                 @bind-Text="@this.DataName" | ||||
|                 Label="Instance Name" | ||||
|                 Class="mb-3" | ||||
|                 MaxLength="40" | ||||
|                 Counter="40" | ||||
|                 Immediate="@true" | ||||
|                 Adornment="Adornment.Start" | ||||
|                 AdornmentIcon="@Icons.Material.Filled.Lightbulb" | ||||
|                 AdornmentColor="Color.Info" | ||||
|                 Validation="@this.providerValidation.ValidatingInstanceName" | ||||
|                 UserAttributes="@SPELLCHECK_ATTRIBUTES" | ||||
|             /> | ||||
|              | ||||
|         </MudForm> | ||||
|         <Issues IssuesData="@this.dataIssues"/> | ||||
|     </DialogContent> | ||||
|     <DialogActions> | ||||
|         <MudButton OnClick="@this.Cancel" Variant="Variant.Filled">Cancel</MudButton> | ||||
|         <MudButton OnClick="@this.Store" Variant="Variant.Filled" Color="Color.Primary"> | ||||
|             @if(this.IsEditing) | ||||
|             { | ||||
|                 @:Update | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 @:Add | ||||
|             } | ||||
|         </MudButton> | ||||
|     </DialogActions> | ||||
| </MudDialog> | ||||
							
								
								
									
										258
									
								
								app/MindWork AI Studio/Dialogs/EmbeddingDialog.razor.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										258
									
								
								app/MindWork AI Studio/Dialogs/EmbeddingDialog.razor.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,258 @@ | ||||
| using AIStudio.Provider; | ||||
| using AIStudio.Settings; | ||||
| using AIStudio.Tools.Validation; | ||||
| 
 | ||||
| using Microsoft.AspNetCore.Components; | ||||
| 
 | ||||
| using Host = AIStudio.Provider.SelfHosted.Host; | ||||
| 
 | ||||
| namespace AIStudio.Dialogs; | ||||
| 
 | ||||
| public partial class EmbeddingDialog : ComponentBase, ISecretId | ||||
| { | ||||
|     [CascadingParameter] | ||||
|     private MudDialogInstance MudDialog { get; set; } = null!; | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// The embedding's number in the list. | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     public uint DataNum { get; set; } | ||||
|      | ||||
|     /// <summary> | ||||
|     /// The embedding's ID. | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     public string DataId { get; set; } = Guid.NewGuid().ToString(); | ||||
|      | ||||
|     /// <summary> | ||||
|     /// The user chosen name. | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     public string DataName { get; set; } = string.Empty; | ||||
|      | ||||
|     /// <summary> | ||||
|     /// The chosen hostname for self-hosted providers. | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     public string DataHostname { get; set; } = string.Empty; | ||||
|      | ||||
|     /// <summary> | ||||
|     /// The host to use, e.g., llama.cpp. | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     public Host DataHost { get; set; } = Host.NONE; | ||||
|      | ||||
|     /// <summary> | ||||
|     /// Is this provider self-hosted? | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     public bool IsSelfHosted { get; set; } | ||||
|      | ||||
|     /// <summary> | ||||
|     /// The provider to use. | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     public LLMProviders DataLLMProvider { get; set; } = LLMProviders.NONE; | ||||
|      | ||||
|     /// <summary> | ||||
|     /// The embedding model to use. | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     public Model DataModel { get; set; } | ||||
|      | ||||
|     /// <summary> | ||||
|     /// Should the dialog be in editing mode? | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     public bool IsEditing { get; init; } | ||||
|      | ||||
|     [Inject] | ||||
|     private SettingsManager SettingsManager { get; init; } = null!; | ||||
|      | ||||
|     [Inject] | ||||
|     private ILogger<ProviderDialog> Logger { get; init; } = null!; | ||||
|      | ||||
|     [Inject] | ||||
|     private RustService RustService { get; init; } = null!; | ||||
|      | ||||
|     private static readonly Dictionary<string, object?> SPELLCHECK_ATTRIBUTES = new(); | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// The list of used instance names. We need this to check for uniqueness. | ||||
|     /// </summary> | ||||
|     private List<string> UsedInstanceNames { get; set; } = []; | ||||
|      | ||||
|     private bool dataIsValid; | ||||
|     private string[] dataIssues = []; | ||||
|     private string dataAPIKey = string.Empty; | ||||
|     private string dataManuallyModel = string.Empty; | ||||
|     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!; | ||||
|      | ||||
|     private readonly List<Model> availableModels = new(); | ||||
|     private readonly Encryption encryption = Program.ENCRYPTION; | ||||
|     private readonly ProviderValidation providerValidation; | ||||
|      | ||||
|     public EmbeddingDialog() | ||||
|     { | ||||
|         this.providerValidation = new() | ||||
|         { | ||||
|             GetProvider = () => this.DataLLMProvider, | ||||
|             GetAPIKeyStorageIssue = () => this.dataAPIKeyStorageIssue, | ||||
|             GetPreviousInstanceName = () => this.dataEditingPreviousInstanceName, | ||||
|             GetUsedInstanceNames = () => this.UsedInstanceNames, | ||||
|             GetHost = () => this.DataHost, | ||||
|         }; | ||||
|     } | ||||
|      | ||||
|     private EmbeddingProvider CreateEmbeddingProviderSettings() | ||||
|     { | ||||
|         var cleanedHostname = this.DataHostname.Trim(); | ||||
|         return new() | ||||
|         { | ||||
|             Num = this.DataNum, | ||||
|             Id = this.DataId, | ||||
|             Name = this.DataName, | ||||
|             UsedLLMProvider = this.DataLLMProvider, | ||||
|             Model = this.DataLLMProvider is LLMProviders.SELF_HOSTED ? new Model(this.dataManuallyModel, null) : this.DataModel, | ||||
|             IsSelfHosted = this.DataLLMProvider is LLMProviders.SELF_HOSTED, | ||||
|             Hostname = cleanedHostname.EndsWith('/') ? cleanedHostname[..^1] : cleanedHostname, | ||||
|             Host = this.DataHost, | ||||
|         }; | ||||
|     } | ||||
|      | ||||
|     #region Overrides of ComponentBase | ||||
| 
 | ||||
|     protected override async Task OnInitializedAsync() | ||||
|     { | ||||
|         // Configure the spellchecking for the instance name input: | ||||
|         this.SettingsManager.InjectSpellchecking(SPELLCHECK_ATTRIBUTES); | ||||
|          | ||||
|         // Load the used instance names: | ||||
|         this.UsedInstanceNames = this.SettingsManager.ConfigurationData.EmbeddingProviders.Select(x => x.Name.ToLowerInvariant()).ToList(); | ||||
|          | ||||
|         // When editing, we need to load the data: | ||||
|         if(this.IsEditing) | ||||
|         { | ||||
|             this.dataEditingPreviousInstanceName = this.DataName.ToLowerInvariant(); | ||||
|              | ||||
|             // When using self-hosted embedding, we must copy the model name: | ||||
|             if (this.DataLLMProvider is LLMProviders.SELF_HOSTED) | ||||
|                 this.dataManuallyModel = this.DataModel.Id; | ||||
|              | ||||
|             // | ||||
|             // We cannot load the API key for self-hosted providers: | ||||
|             // | ||||
|             if (this.DataLLMProvider is LLMProviders.SELF_HOSTED && this.DataHost is not Host.OLLAMA) | ||||
|             { | ||||
|                 await this.ReloadModels(); | ||||
|                 await base.OnInitializedAsync(); | ||||
|                 return; | ||||
|             } | ||||
|              | ||||
|             // Load the API key: | ||||
|             var requestedSecret = await this.RustService.GetAPIKey(this, isTrying: this.DataLLMProvider is LLMProviders.SELF_HOSTED); | ||||
|             if (requestedSecret.Success) | ||||
|                 this.dataAPIKey = await requestedSecret.Secret.Decrypt(this.encryption); | ||||
|             else | ||||
|             { | ||||
|                 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(); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             await this.ReloadModels(); | ||||
|         } | ||||
|          | ||||
|         await base.OnInitializedAsync(); | ||||
|     } | ||||
| 
 | ||||
|     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(); | ||||
|          | ||||
|         await base.OnAfterRenderAsync(firstRender); | ||||
|     } | ||||
| 
 | ||||
|     #endregion | ||||
|      | ||||
|     #region Implementation of ISecretId | ||||
| 
 | ||||
|     public string SecretId => this.DataId; | ||||
|      | ||||
|     public string SecretName => this.DataName; | ||||
| 
 | ||||
|     #endregion | ||||
|      | ||||
|     private async Task Store() | ||||
|     { | ||||
|         await this.form.Validate(); | ||||
|         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 addedProviderSettings = this.CreateEmbeddingProviderSettings(); | ||||
|         if (!string.IsNullOrWhiteSpace(this.dataAPIKey)) | ||||
|         { | ||||
|             // Store the API key in the OS secure storage: | ||||
|             var storeResponse = await this.RustService.SetAPIKey(this, this.dataAPIKey); | ||||
|             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; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         this.MudDialog.Close(DialogResult.Ok(addedProviderSettings)); | ||||
|     } | ||||
|      | ||||
|     private string? ValidateManuallyModel(string manuallyModel) | ||||
|     { | ||||
|         if (this.DataLLMProvider is LLMProviders.SELF_HOSTED && string.IsNullOrWhiteSpace(manuallyModel)) | ||||
|             return "Please enter an embedding model name."; | ||||
|          | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     private void Cancel() => this.MudDialog.Cancel(); | ||||
|      | ||||
|     private async Task ReloadModels() | ||||
|     { | ||||
|         var currentEmbeddingProviderSettings = this.CreateEmbeddingProviderSettings(); | ||||
|         var provider = currentEmbeddingProviderSettings.CreateProvider(this.Logger); | ||||
|         if(provider is NoProvider) | ||||
|             return; | ||||
|          | ||||
|         var models = await provider.GetEmbeddingModels(this.dataAPIKey); | ||||
|          | ||||
|         // 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); | ||||
|     } | ||||
|      | ||||
|     private string APIKeyText => this.DataLLMProvider switch | ||||
|     { | ||||
|         LLMProviders.SELF_HOSTED => "(Optional) API Key", | ||||
|         _ => "API Key", | ||||
|     }; | ||||
|      | ||||
|     private bool IsNoneProvider => this.DataLLMProvider is LLMProviders.NONE; | ||||
| } | ||||
| @ -6,13 +6,13 @@ | ||||
|         <MudForm @ref="@this.form" @bind-IsValid="@this.dataIsValid" @bind-Errors="@this.dataIssues"> | ||||
|             <MudStack Row="@true" AlignItems="AlignItems.Center"> | ||||
|                 @* ReSharper disable once CSharpWarnings::CS8974 *@ | ||||
|                 <MudSelect @bind-Value="@this.DataLLMProvider" Label="Provider" Class="mb-3" OpenIcon="@Icons.Material.Filled.AccountBalance" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.ValidatingProvider"> | ||||
|                 <MudSelect @bind-Value="@this.DataLLMProvider" Label="Provider" Class="mb-3" OpenIcon="@Icons.Material.Filled.AccountBalance" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingProvider"> | ||||
|                     @foreach (LLMProviders provider in Enum.GetValues(typeof(LLMProviders))) | ||||
|                     { | ||||
|                         <MudSelectItem Value="@provider">@provider</MudSelectItem> | ||||
|                     } | ||||
|                 </MudSelect> | ||||
|                 <MudButton Disabled="@(!this.ShowRegisterButton)" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.GetProviderCreationURL()" Target="_blank">Create account</MudButton> | ||||
|                 <MudButton Disabled="@(!this.DataLLMProvider.ShowRegisterButton())" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.DataLLMProvider.GetCreationURL()" Target="_blank">Create account</MudButton> | ||||
|             </MudStack> | ||||
|              | ||||
|             @* ReSharper disable once CSharpWarnings::CS8974 *@ | ||||
| @ -20,29 +20,29 @@ | ||||
|                 T="string" | ||||
|                 @bind-Text="@this.dataAPIKey" | ||||
|                 Label="@this.APIKeyText" | ||||
|                 Disabled="@(!this.NeedAPIKey)" | ||||
|                 Disabled="@(!this.DataLLMProvider.IsAPIKeyNeeded(this.DataHost))" | ||||
|                 Class="mb-3" | ||||
|                 Adornment="Adornment.Start" | ||||
|                 AdornmentIcon="@Icons.Material.Filled.VpnKey" | ||||
|                 AdornmentColor="Color.Info" | ||||
|                 InputType="InputType.Password" | ||||
|                 Validation="@this.ValidatingAPIKey" | ||||
|                 Validation="@this.providerValidation.ValidatingAPIKey" | ||||
|             /> | ||||
|              | ||||
|             <MudTextField | ||||
|                 T="string" | ||||
|                 @bind-Text="@this.DataHostname" | ||||
|                 Label="Hostname" | ||||
|                 Disabled="@(!this.NeedHostname)" | ||||
|                 Disabled="@(!this.DataLLMProvider.IsHostnameNeeded())" | ||||
|                 Class="mb-3" | ||||
|                 Adornment="Adornment.Start" | ||||
|                 AdornmentIcon="@Icons.Material.Filled.Dns" | ||||
|                 AdornmentColor="Color.Info" | ||||
|                 Validation="@this.ValidatingHostname" | ||||
|                 Validation="@this.providerValidation.ValidatingHostname" | ||||
|                 UserAttributes="@SPELLCHECK_ATTRIBUTES" | ||||
|             /> | ||||
| 
 | ||||
|             <MudSelect Disabled="@(!this.NeedHost)" @bind-Value="@this.DataHost" Label="Host" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.ValidatingHost"> | ||||
|             <MudSelect Disabled="@(!this.DataLLMProvider.IsHostNeeded())" @bind-Value="@this.DataHost" Label="Host" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingHost"> | ||||
|                 @foreach (Host host in Enum.GetValues(typeof(Host))) | ||||
|                 { | ||||
|                     <MudSelectItem Value="@host">@host.Name()</MudSelectItem> | ||||
| @ -50,9 +50,9 @@ | ||||
|             </MudSelect> | ||||
| 
 | ||||
|             <MudStack Row="@true" AlignItems="AlignItems.Center"> | ||||
|                 @if (this.ProvideModelManually) | ||||
|                 @if (this.DataLLMProvider.IsLLMModelProvidedManually()) | ||||
|                 { | ||||
|                     <MudButton Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.GetModelOverviewURL()" Target="_blank">Show available models</MudButton> | ||||
|                     <MudButton Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.DataLLMProvider.GetModelsOverviewURL()" Target="_blank">Show available models</MudButton> | ||||
|                     <MudTextField | ||||
|                         T="string" | ||||
|                         @bind-Text="@this.dataManuallyModel" | ||||
| @ -67,8 +67,8 @@ | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     <MudButton Disabled="@(!this.CanLoadModels())" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="this.ReloadModels">Load</MudButton> | ||||
|                     <MudSelect Disabled="@this.IsNoneProvider" @bind-Value="@this.DataModel" Label="Model" Class="mb-3" OpenIcon="@Icons.Material.Filled.FaceRetouchingNatural" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.ValidatingModel"> | ||||
|                     <MudButton Disabled="@(!this.DataLLMProvider.CanLoadModels(this.DataHost, this.dataAPIKey))" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="this.ReloadModels">Load</MudButton> | ||||
|                     <MudSelect Disabled="@this.IsNoneProvider" @bind-Value="@this.DataModel" Label="Model" Class="mb-3" OpenIcon="@Icons.Material.Filled.FaceRetouchingNatural" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingModel"> | ||||
|                         @foreach (var model in this.availableModels) | ||||
|                         { | ||||
|                             <MudSelectItem Value="@model">@model</MudSelectItem> | ||||
| @ -89,7 +89,7 @@ | ||||
|                 Adornment="Adornment.Start" | ||||
|                 AdornmentIcon="@Icons.Material.Filled.Lightbulb" | ||||
|                 AdornmentColor="Color.Info" | ||||
|                 Validation="@this.ValidatingInstanceName" | ||||
|                 Validation="@this.providerValidation.ValidatingInstanceName" | ||||
|                 UserAttributes="@SPELLCHECK_ATTRIBUTES" | ||||
|             /> | ||||
|              | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| using AIStudio.Provider; | ||||
| using AIStudio.Settings; | ||||
| using AIStudio.Tools.Validation; | ||||
| 
 | ||||
| using Microsoft.AspNetCore.Components; | ||||
| 
 | ||||
| @ -11,7 +12,7 @@ namespace AIStudio.Dialogs; | ||||
| /// <summary> | ||||
| /// The provider settings dialog. | ||||
| /// </summary> | ||||
| public partial class ProviderDialog : ComponentBase | ||||
| public partial class ProviderDialog : ComponentBase, ISecretId | ||||
| { | ||||
|     [CascadingParameter] | ||||
|     private MudDialogInstance MudDialog { get; set; } = null!; | ||||
| @ -41,7 +42,7 @@ public partial class ProviderDialog : ComponentBase | ||||
|     public string DataHostname { get; set; } = string.Empty; | ||||
|      | ||||
|     /// <summary> | ||||
|     /// The local host to use, e.g., llama.cpp. | ||||
|     /// The host to use, e.g., llama.cpp. | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     public Host DataHost { get; set; } = Host.NONE; | ||||
| @ -98,6 +99,19 @@ public partial class ProviderDialog : ComponentBase | ||||
|      | ||||
|     private readonly List<Model> availableModels = new(); | ||||
|     private readonly Encryption encryption = Program.ENCRYPTION; | ||||
|     private readonly ProviderValidation providerValidation; | ||||
| 
 | ||||
|     public ProviderDialog() | ||||
|     { | ||||
|         this.providerValidation = new() | ||||
|         { | ||||
|             GetProvider = () => this.DataLLMProvider, | ||||
|             GetAPIKeyStorageIssue = () => this.dataAPIKeyStorageIssue, | ||||
|             GetPreviousInstanceName = () => this.dataEditingPreviousInstanceName, | ||||
|             GetUsedInstanceNames = () => this.UsedInstanceNames, | ||||
|             GetHost = () => this.DataHost, | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     private Settings.Provider CreateProviderSettings() | ||||
|     { | ||||
| @ -144,23 +158,10 @@ public partial class ProviderDialog : ComponentBase | ||||
|                 return; | ||||
|             } | ||||
|              | ||||
|             var loadedProviderSettings = this.CreateProviderSettings(); | ||||
|             var provider = loadedProviderSettings.CreateProvider(this.Logger); | ||||
|             if(provider is NoProvider) | ||||
|             { | ||||
|                 await base.OnInitializedAsync(); | ||||
|                 return; | ||||
|             } | ||||
|              | ||||
|             // Load the API key: | ||||
|             var requestedSecret = await this.RustService.GetAPIKey(provider, isTrying: this.DataLLMProvider is LLMProviders.SELF_HOSTED); | ||||
|             if(requestedSecret.Success) | ||||
|             { | ||||
|             var requestedSecret = await this.RustService.GetAPIKey(this, isTrying: this.DataLLMProvider is LLMProviders.SELF_HOSTED); | ||||
|             if (requestedSecret.Success) | ||||
|                 this.dataAPIKey = await requestedSecret.Secret.Decrypt(this.encryption); | ||||
|                  | ||||
|                 // Now, we try to load the list of available models: | ||||
|                 await this.ReloadModels(); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 this.dataAPIKey = string.Empty; | ||||
| @ -169,10 +170,9 @@ public partial class ProviderDialog : ComponentBase | ||||
|                     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(); | ||||
|             } | ||||
| 
 | ||||
|             await this.ReloadModels(); | ||||
|         } | ||||
|          | ||||
|         await base.OnInitializedAsync(); | ||||
| @ -189,7 +189,15 @@ public partial class ProviderDialog : ComponentBase | ||||
|     } | ||||
| 
 | ||||
|     #endregion | ||||
| 
 | ||||
|     #region Implementation of ISecretId | ||||
| 
 | ||||
|     public string SecretId => this.DataLLMProvider.ToName(); | ||||
|      | ||||
|     public string SecretName => this.DataInstanceName; | ||||
| 
 | ||||
|     #endregion | ||||
| 
 | ||||
|     private async Task Store() | ||||
|     { | ||||
|         await this.form.Validate(); | ||||
| @ -205,11 +213,8 @@ public partial class ProviderDialog : ComponentBase | ||||
|         var addedProviderSettings = this.CreateProviderSettings(); | ||||
|         if (!string.IsNullOrWhiteSpace(this.dataAPIKey)) | ||||
|         { | ||||
|             // We need to instantiate the provider to store the API key: | ||||
|             var provider = addedProviderSettings.CreateProvider(this.Logger); | ||||
|              | ||||
|             // Store the API key in the OS secure storage: | ||||
|             var storeResponse = await this.RustService.SetAPIKey(provider, this.dataAPIKey); | ||||
|             var storeResponse = await this.RustService.SetAPIKey(this, this.dataAPIKey); | ||||
|             if (!storeResponse.Success) | ||||
|             { | ||||
|                 this.dataAPIKeyStorageIssue = $"Failed to store the API key in the operating system. The message was: {storeResponse.Issue}. Please try again."; | ||||
| @ -221,25 +226,6 @@ public partial class ProviderDialog : ComponentBase | ||||
|         this.MudDialog.Close(DialogResult.Ok(addedProviderSettings)); | ||||
|     } | ||||
|      | ||||
|     private string? ValidatingProvider(LLMProviders llmProvider) | ||||
|     { | ||||
|         if (llmProvider == LLMProviders.NONE) | ||||
|             return "Please select a provider."; | ||||
|          | ||||
|         return null; | ||||
|     } | ||||
|      | ||||
|     private string? ValidatingHost(Host host) | ||||
|     { | ||||
|         if(this.DataLLMProvider is not LLMProviders.SELF_HOSTED) | ||||
|             return null; | ||||
| 
 | ||||
|         if (host == Host.NONE) | ||||
|             return "Please select a host."; | ||||
| 
 | ||||
|         return null; | ||||
|     } | ||||
|      | ||||
|     private string? ValidateManuallyModel(string manuallyModel) | ||||
|     { | ||||
|         if (this.DataLLMProvider is LLMProviders.FIREWORKS && string.IsNullOrWhiteSpace(manuallyModel)) | ||||
| @ -247,64 +233,6 @@ public partial class ProviderDialog : ComponentBase | ||||
|          | ||||
|         return null; | ||||
|     } | ||||
|      | ||||
|     private string? ValidatingModel(Model model) | ||||
|     { | ||||
|         if(this.DataLLMProvider is LLMProviders.SELF_HOSTED && this.DataHost == Host.LLAMACPP) | ||||
|             return null; | ||||
|          | ||||
|         if (model == default) | ||||
|             return "Please select a model."; | ||||
|          | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     private string? ValidatingInstanceName(string instanceName) | ||||
|     { | ||||
|         if (string.IsNullOrWhiteSpace(instanceName)) | ||||
|             return "Please enter an instance name."; | ||||
|          | ||||
|         if (instanceName.Length > 40) | ||||
|             return "The instance name must not exceed 40 characters."; | ||||
|          | ||||
|         // The instance name must be unique: | ||||
|         var lowerInstanceName = instanceName.ToLowerInvariant(); | ||||
|         if (lowerInstanceName != this.dataEditingPreviousInstanceName && this.UsedInstanceNames.Contains(lowerInstanceName)) | ||||
|             return "The instance name must be unique; the chosen name is already in use."; | ||||
|          | ||||
|         return null; | ||||
|     } | ||||
|      | ||||
|     private string? ValidatingAPIKey(string apiKey) | ||||
|     { | ||||
|         if(this.DataLLMProvider is LLMProviders.SELF_HOSTED) | ||||
|             return null; | ||||
|          | ||||
|         if(!string.IsNullOrWhiteSpace(this.dataAPIKeyStorageIssue)) | ||||
|             return this.dataAPIKeyStorageIssue; | ||||
| 
 | ||||
|         if(string.IsNullOrWhiteSpace(apiKey)) | ||||
|             return "Please enter an API key."; | ||||
|          | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     private string? ValidatingHostname(string hostname) | ||||
|     { | ||||
|         if(this.DataLLMProvider != LLMProviders.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://"; | ||||
| 
 | ||||
|         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(); | ||||
|      | ||||
| @ -324,109 +252,11 @@ public partial class ProviderDialog : ComponentBase | ||||
|         this.availableModels.AddRange(orderedModels); | ||||
|     } | ||||
|      | ||||
|     private bool CanLoadModels() | ||||
|     { | ||||
|         if (this.DataLLMProvider is LLMProviders.SELF_HOSTED) | ||||
|         { | ||||
|             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; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if(this.DataLLMProvider is LLMProviders.NONE) | ||||
|             return false; | ||||
|          | ||||
|         if(string.IsNullOrWhiteSpace(this.dataAPIKey)) | ||||
|             return false; | ||||
|          | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     private bool ShowRegisterButton => this.DataLLMProvider switch | ||||
|     { | ||||
|         LLMProviders.OPEN_AI => true, | ||||
|         LLMProviders.MISTRAL => true, | ||||
|         LLMProviders.ANTHROPIC => true, | ||||
|         LLMProviders.GOOGLE => true, | ||||
|          | ||||
|         LLMProviders.GROQ => true, | ||||
|         LLMProviders.FIREWORKS => true, | ||||
|          | ||||
|         _ => false, | ||||
|     }; | ||||
| 
 | ||||
|     private bool NeedAPIKey => this.DataLLMProvider switch | ||||
|     { | ||||
|         LLMProviders.OPEN_AI => true, | ||||
|         LLMProviders.MISTRAL => true, | ||||
|         LLMProviders.ANTHROPIC => true, | ||||
|         LLMProviders.GOOGLE => true, | ||||
|          | ||||
|         LLMProviders.GROQ => true, | ||||
|         LLMProviders.FIREWORKS => true, | ||||
|          | ||||
|         LLMProviders.SELF_HOSTED => this.DataHost is Host.OLLAMA, | ||||
|          | ||||
|         _ => false, | ||||
|     }; | ||||
|      | ||||
|     private string APIKeyText => this.DataLLMProvider switch | ||||
|     { | ||||
|         LLMProviders.SELF_HOSTED => "(Optional) API Key", | ||||
|         _ => "API Key", | ||||
|     }; | ||||
| 
 | ||||
|     private bool NeedHostname => this.DataLLMProvider switch | ||||
|     { | ||||
|         LLMProviders.SELF_HOSTED => true, | ||||
|         _ => false, | ||||
|     }; | ||||
|      | ||||
|     private bool NeedHost => this.DataLLMProvider switch | ||||
|     { | ||||
|         LLMProviders.SELF_HOSTED => true, | ||||
|         _ => false, | ||||
|     }; | ||||
|      | ||||
|     private bool ProvideModelManually => this.DataLLMProvider switch | ||||
|     { | ||||
|         LLMProviders.FIREWORKS => true, | ||||
|         _ => false, | ||||
|     }; | ||||
|      | ||||
|     private string GetModelOverviewURL() => this.DataLLMProvider switch | ||||
|     { | ||||
|         LLMProviders.FIREWORKS => "https://fireworks.ai/models?show=Serverless", | ||||
|          | ||||
|         _ => string.Empty, | ||||
|     }; | ||||
| 
 | ||||
|     private string GetProviderCreationURL() => this.DataLLMProvider switch | ||||
|     { | ||||
|         LLMProviders.OPEN_AI => "https://platform.openai.com/signup", | ||||
|         LLMProviders.MISTRAL => "https://console.mistral.ai/", | ||||
|         LLMProviders.ANTHROPIC => "https://console.anthropic.com/dashboard", | ||||
|         LLMProviders.GOOGLE => "https://console.cloud.google.com/", | ||||
|       | ||||
|         LLMProviders.GROQ => "https://console.groq.com/", | ||||
|         LLMProviders.FIREWORKS => "https://fireworks.ai/login", | ||||
|          | ||||
|         _ => string.Empty, | ||||
|     }; | ||||
|      | ||||
|     private bool IsNoneProvider => this.DataLLMProvider is LLMProviders.NONE; | ||||
| } | ||||
| @ -40,11 +40,11 @@ | ||||
|                     <MudTd> | ||||
|                         @if (context.UsedLLMProvider is not LLMProviders.SELF_HOSTED) | ||||
|                         { | ||||
|                             @this.GetProviderModelName(context) | ||||
|                             @this.GetLLMProviderModelName(context) | ||||
|                         } | ||||
|                         else if (context.UsedLLMProvider is LLMProviders.SELF_HOSTED && context.Host is not Host.LLAMACPP) | ||||
|                         { | ||||
|                             @this.GetProviderModelName(context) | ||||
|                             @this.GetLLMProviderModelName(context) | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
| @ -52,13 +52,13 @@ | ||||
|                         } | ||||
|                     </MudTd> | ||||
|                     <MudTd Style="text-align: left;"> | ||||
|                         <MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.OpenInBrowser" Class="ma-2" Href="@this.GetProviderDashboardURL(context.UsedLLMProvider)" Target="_blank" Disabled="@(!this.HasDashboard(context.UsedLLMProvider))"> | ||||
|                         <MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.OpenInBrowser" Class="ma-2" Href="@context.UsedLLMProvider.GetDashboardURL()" Target="_blank" Disabled="@(!context.UsedLLMProvider.HasDashboard())"> | ||||
|                             Open Dashboard | ||||
|                         </MudButton> | ||||
|                         <MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Edit" Class="ma-2" OnClick="() => this.EditProvider(context)"> | ||||
|                         <MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Edit" Class="ma-2" OnClick="() => this.EditLLMProvider(context)"> | ||||
|                             Edit | ||||
|                         </MudButton> | ||||
|                         <MudButton Variant="Variant.Filled" Color="Color.Error" StartIcon="@Icons.Material.Filled.Delete" Class="ma-2" OnClick="() => this.DeleteProvider(context)"> | ||||
|                         <MudButton Variant="Variant.Filled" Color="Color.Error" StartIcon="@Icons.Material.Filled.Delete" Class="ma-2" OnClick="() => this.DeleteLLMProvider(context)"> | ||||
|                             Delete | ||||
|                         </MudButton> | ||||
|                     </MudTd> | ||||
| @ -70,7 +70,7 @@ | ||||
|                 <MudText Typo="Typo.h6" Class="mt-3">No providers configured yet.</MudText> | ||||
|             } | ||||
| 
 | ||||
|             <MudButton Variant="Variant.Filled" Color="@Color.Primary" StartIcon="@Icons.Material.Filled.AddRoad" Class="mt-3 mb-6" OnClick="@this.AddProvider"> | ||||
|             <MudButton Variant="Variant.Filled" Color="@Color.Primary" StartIcon="@Icons.Material.Filled.AddRoad" Class="mt-3 mb-6" OnClick="@this.AddLLMProvider"> | ||||
|                 Add Provider | ||||
|             </MudButton> | ||||
|              | ||||
| @ -131,6 +131,73 @@ | ||||
| 
 | ||||
|         </ExpansionPanel> | ||||
| 
 | ||||
|         @if (this.SettingsManager.ConfigurationData.App.PreviewVisibility >= PreviewVisibility.PROTOTYPE) | ||||
|         { | ||||
|             <ExpansionPanel HeaderIcon="@Icons.Material.Filled.IntegrationInstructions" HeaderText="Configure Embeddings"> | ||||
|                 <PreviewPrototype/> | ||||
|                 <MudText Typo="Typo.h4" Class="mb-3"> | ||||
|                     Configured Embeddings | ||||
|                 </MudText> | ||||
|                 <MudJustifiedText Typo="Typo.body1" Class="mb-3"> | ||||
|                     Embeddings are a way to represent words, sentences, entire documents, or even images and videos as digital | ||||
|                     fingerprints. Just like each person has a unique fingerprint, embedding models create unique digital patterns | ||||
|                     that capture the meaning and characteristics of the content they analyze. When two things are similar in meaning | ||||
|                     or content, their digital fingerprints will look very similar. For example, the fingerprints for 'happy' and | ||||
|                     'joyful' would be more alike than those for 'happy' and 'sad'. | ||||
|                 </MudJustifiedText> | ||||
| 
 | ||||
|                 <MudJustifiedText Typo="Typo.body1" Class="mb-3"> | ||||
|                     This helps AI Studio understand and compare things in a way that's similar to how humans do. When you're working on | ||||
|                     something, AI Studio can automatically identify related documents and data by comparing their digital fingerprints. | ||||
|                     For instance, if you're writing about customer service, AI Studio can instantly find other documents in your data that | ||||
|                     discuss similar topics or experiences, even if they use different words. | ||||
|                 </MudJustifiedText> | ||||
|                 <MudTable Items="@this.SettingsManager.ConfigurationData.EmbeddingProviders" Hover="@true" Class="border-dashed border rounded-lg"> | ||||
|                     <ColGroup> | ||||
|                         <col style="width: 3em;"/> | ||||
|                         <col style="width: 12em;"/> | ||||
|                         <col style="width: 12em;"/> | ||||
|                         <col/> | ||||
|                         <col style="width: 40em;"/> | ||||
|                     </ColGroup> | ||||
|                     <HeaderContent> | ||||
|                         <MudTh>#</MudTh> | ||||
|                         <MudTh>Name</MudTh> | ||||
|                         <MudTh>Provider</MudTh> | ||||
|                         <MudTh>Model</MudTh> | ||||
|                         <MudTh Style="text-align: left;">Actions</MudTh> | ||||
|                     </HeaderContent> | ||||
|                     <RowTemplate> | ||||
|                         <MudTd>@context.Num</MudTd> | ||||
|                         <MudTd>@context.Name</MudTd> | ||||
|                         <MudTd>@context.UsedLLMProvider</MudTd> | ||||
|                         <MudTd>@this.GetEmbeddingProviderModelName(context)</MudTd> | ||||
|                          | ||||
|                         <MudTd Style="text-align: left;"> | ||||
|                             <MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.OpenInBrowser" Class="ma-2" Href="@context.UsedLLMProvider.GetDashboardURL()" Target="_blank" Disabled="@(!context.UsedLLMProvider.HasDashboard())"> | ||||
|                                 Open Dashboard | ||||
|                             </MudButton> | ||||
|                             <MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Edit" Class="ma-2" OnClick="() => this.EditEmbeddingProvider(context)"> | ||||
|                                 Edit | ||||
|                             </MudButton> | ||||
|                             <MudButton Variant="Variant.Filled" Color="Color.Error" StartIcon="@Icons.Material.Filled.Delete" Class="ma-2" OnClick="() => this.DeleteEmbeddingProvider(context)"> | ||||
|                                 Delete | ||||
|                             </MudButton> | ||||
|                         </MudTd> | ||||
|                     </RowTemplate> | ||||
|                 </MudTable> | ||||
| 
 | ||||
|                 @if (this.SettingsManager.ConfigurationData.EmbeddingProviders.Count == 0) | ||||
|                 { | ||||
|                     <MudText Typo="Typo.h6" Class="mt-3">No embeddings configured yet.</MudText> | ||||
|                 } | ||||
| 
 | ||||
|                 <MudButton Variant="Variant.Filled" Color="@Color.Primary" StartIcon="@Icons.Material.Filled.AddRoad" Class="mt-3 mb-6" OnClick="@this.AddEmbeddingProvider"> | ||||
|                     Add Embedding | ||||
|                 </MudButton> | ||||
|             </ExpansionPanel> | ||||
|         } | ||||
| 
 | ||||
|         <ExpansionPanel HeaderIcon="@Icons.Material.Filled.Person4" HeaderText="Configure Profiles"> | ||||
|             <MudText Typo="Typo.h4" Class="mb-3">Your Profiles</MudText> | ||||
|             <MudJustifiedText Typo="Typo.body1" Class="mb-3"> | ||||
| @ -187,7 +254,7 @@ | ||||
|             <ConfigurationSelect OptionDescription="Check for updates" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.UpdateBehavior)" Data="@ConfigurationSelectDataFactory.GetUpdateBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.UpdateBehavior = selectedValue)" OptionHelp="How often should we check for app updates?"/> | ||||
|             <ConfigurationSelect OptionDescription="Navigation bar behavior" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.NavigationBehavior)" Data="@ConfigurationSelectDataFactory.GetNavBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.NavigationBehavior = selectedValue)" OptionHelp="Select the desired behavior for the navigation bar."/> | ||||
|             <ConfigurationSelect OptionDescription="Preview feature visibility" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.PreviewVisibility)" Data="@ConfigurationSelectDataFactory.GetPreviewVisibility()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.PreviewVisibility = selectedValue)" OptionHelp="Do you want to show preview features in the app?"/> | ||||
|             <ConfigurationProviderSelection Data="@this.availableProviders" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.PreselectedProvider = selectedValue)" HelpText="@(() => "Would you like to set one provider as the default for the entire app? When you configure a different provider for an assistant, it will always take precedence.")"/> | ||||
|             <ConfigurationProviderSelection Data="@this.availableLLMProviders" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.PreselectedProvider = selectedValue)" HelpText="@(() => "Would you like to set one provider as the default for the entire app? When you configure a different provider for an assistant, it will always take precedence.")"/> | ||||
|             <ConfigurationSelect OptionDescription="Preselect one of your profiles?" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.PreselectedProfile)" Data="@ConfigurationSelectDataFactory.GetProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.PreselectedProfile = selectedValue)" OptionHelp="Would you like to set one of your profiles as the default for the entire app? When you configure a different profile for an assistant, it will always take precedence."/> | ||||
|         </ExpansionPanel> | ||||
|          | ||||
| @ -199,7 +266,7 @@ | ||||
| 
 | ||||
|             <MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg"> | ||||
|                 <ConfigurationOption OptionDescription="Preselect chat options?" LabelOn="Chat options are preselected" LabelOff="No chat options are preselected" State="@(() => this.SettingsManager.ConfigurationData.Chat.PreselectOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.Chat.PreselectOptions = updatedState)" OptionHelp="When enabled, you can preselect chat options. This is might be useful when you prefer a specific provider."/> | ||||
|                 <ConfigurationProviderSelection Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.Chat.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Chat.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Chat.PreselectedProvider = selectedValue)"/> | ||||
|                 <ConfigurationProviderSelection Data="@this.availableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.Chat.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Chat.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Chat.PreselectedProvider = selectedValue)"/> | ||||
|                 <ConfigurationSelect OptionDescription="Preselect one of your profiles?" Disabled="@(() => !this.SettingsManager.ConfigurationData.Chat.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Chat.PreselectedProfile)" Data="@ConfigurationSelectDataFactory.GetProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Chat.PreselectedProfile = selectedValue)" OptionHelp="Would you like to set one of your profiles as the default for chats?"/> | ||||
|             </MudPaper> | ||||
|         </ExpansionPanel> | ||||
| @ -219,7 +286,7 @@ | ||||
|                 <ConfigurationOption OptionDescription="Preselect icon options?" LabelOn="Icon options are preselected" LabelOff="No icon options are preselected" State="@(() => this.SettingsManager.ConfigurationData.IconFinder.PreselectOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.IconFinder.PreselectOptions = updatedState)" OptionHelp="When enabled, you can preselect the icon options. This is might be useful when you prefer a specific icon source or LLM model."/> | ||||
|                 <ConfigurationSelect OptionDescription="Preselect the icon source" Disabled="@(() => !this.SettingsManager.ConfigurationData.IconFinder.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.IconFinder.PreselectedSource)" Data="@ConfigurationSelectDataFactory.GetIconSourcesData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.IconFinder.PreselectedSource = selectedValue)" OptionHelp="Which icon source should be preselected?"/> | ||||
|                 <ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.IconFinder.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.IconFinder.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.IconFinder.MinimumProviderConfidence = selectedValue)"/> | ||||
|                 <ConfigurationProviderSelection Component="Components.ICON_FINDER_ASSISTANT" Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.IconFinder.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.IconFinder.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.IconFinder.PreselectedProvider = selectedValue)"/> | ||||
|                 <ConfigurationProviderSelection Component="Components.ICON_FINDER_ASSISTANT" Data="@this.availableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.IconFinder.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.IconFinder.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.IconFinder.PreselectedProvider = selectedValue)"/> | ||||
|             </MudPaper> | ||||
|         </ExpansionPanel> | ||||
|          | ||||
| @ -237,7 +304,7 @@ | ||||
|                     <ConfigurationText OptionDescription="Preselect another target language" Disabled="@(() => !this.SettingsManager.ConfigurationData.Translation.PreselectOptions)" Icon="@Icons.Material.Filled.Translate" Text="@(() => this.SettingsManager.ConfigurationData.Translation.PreselectOtherLanguage)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.Translation.PreselectOtherLanguage = updatedText)"/> | ||||
|                 } | ||||
|                 <ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.Translation.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Translation.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Translation.MinimumProviderConfidence = selectedValue)"/> | ||||
|                 <ConfigurationProviderSelection Component="Components.TRANSLATION_ASSISTANT" Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.Translation.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Translation.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Translation.PreselectedProvider = selectedValue)"/> | ||||
|                 <ConfigurationProviderSelection Component="Components.TRANSLATION_ASSISTANT" Data="@this.availableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.Translation.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Translation.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Translation.PreselectedProvider = selectedValue)"/> | ||||
|             </MudPaper> | ||||
|         </ExpansionPanel> | ||||
|          | ||||
| @ -251,7 +318,7 @@ | ||||
|                     <ConfigurationText OptionDescription="Preselect another programming language" Disabled="@(() => !this.SettingsManager.ConfigurationData.Coding.PreselectOptions)" Icon="@Icons.Material.Filled.Code" Text="@(() => this.SettingsManager.ConfigurationData.Coding.PreselectedOtherProgrammingLanguage)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.Coding.PreselectedOtherProgrammingLanguage = updatedText)"/> | ||||
|                 } | ||||
|                                             <ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.Coding.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Coding.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Coding.MinimumProviderConfidence = selectedValue)"/> | ||||
|                 <ConfigurationProviderSelection Component="Components.CODING_ASSISTANT" Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.Coding.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Coding.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Coding.PreselectedProvider = selectedValue)"/> | ||||
|                 <ConfigurationProviderSelection Component="Components.CODING_ASSISTANT" Data="@this.availableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.Coding.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Coding.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Coding.PreselectedProvider = selectedValue)"/> | ||||
|                 <ConfigurationSelect OptionDescription="Preselect one of your profiles?" Disabled="@(() => !this.SettingsManager.ConfigurationData.Coding.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Coding.PreselectedProfile)" Data="@ConfigurationSelectDataFactory.GetProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Coding.PreselectedProfile = selectedValue)" OptionHelp="Would you like to preselect one of your profiles?"/> | ||||
|             </MudPaper> | ||||
|         </ExpansionPanel> | ||||
| @ -273,7 +340,7 @@ | ||||
|                     <ConfigurationText OptionDescription="Preselect your expertise" Disabled="@(() => !this.SettingsManager.ConfigurationData.TextSummarizer.PreselectOptions)" Icon="@Icons.Material.Filled.Person" Text="@(() => this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedExpertInField)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedExpertInField = updatedText)"/> | ||||
|                 } | ||||
|                 <ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.TextSummarizer.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.TextSummarizer.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.TextSummarizer.MinimumProviderConfidence = selectedValue)"/> | ||||
|                 <ConfigurationProviderSelection Component="Components.TEXT_SUMMARIZER_ASSISTANT" Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.TextSummarizer.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedProvider = selectedValue)"/> | ||||
|                 <ConfigurationProviderSelection Component="Components.TEXT_SUMMARIZER_ASSISTANT" Data="@this.availableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.TextSummarizer.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedProvider = selectedValue)"/> | ||||
|             </MudPaper> | ||||
|         </ExpansionPanel> | ||||
|          | ||||
| @ -302,7 +369,7 @@ | ||||
|                     <ConfigurationText OptionDescription="Preselect another agenda language" Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" Icon="@Icons.Material.Filled.Translate" Text="@(() => this.SettingsManager.ConfigurationData.Agenda.PreselectedOtherLanguage)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.Agenda.PreselectedOtherLanguage = updatedText)"/> | ||||
|                 } | ||||
|                 <ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Agenda.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Agenda.MinimumProviderConfidence = selectedValue)"/> | ||||
|                 <ConfigurationProviderSelection Component="Components.AGENDA_ASSISTANT" Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Agenda.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Agenda.PreselectedProvider = selectedValue)"/> | ||||
|                 <ConfigurationProviderSelection Component="Components.AGENDA_ASSISTANT" Data="@this.availableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Agenda.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Agenda.PreselectedProvider = selectedValue)"/> | ||||
|                 <ConfigurationSelect OptionDescription="Preselect one of your profiles?" Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Agenda.PreselectedProfile)" Data="@ConfigurationSelectDataFactory.GetProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Agenda.PreselectedProfile = selectedValue)" OptionHelp="Would you like to preselect one of your profiles?"/> | ||||
|             </MudPaper> | ||||
|         </ExpansionPanel> | ||||
| @ -316,7 +383,7 @@ | ||||
|                     <ConfigurationText OptionDescription="Preselect another target language" Disabled="@(() => !this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectOptions)" Icon="@Icons.Material.Filled.Translate" Text="@(() => this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectedOtherLanguage)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectedOtherLanguage = updatedText)"/> | ||||
|                 } | ||||
|                 <ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.GrammarSpelling.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.GrammarSpelling.MinimumProviderConfidence = selectedValue)"/> | ||||
|                 <ConfigurationProviderSelection Component="Components.GRAMMAR_SPELLING_ASSISTANT" Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectedProvider = selectedValue)"/> | ||||
|                 <ConfigurationProviderSelection Component="Components.GRAMMAR_SPELLING_ASSISTANT" Data="@this.availableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectedProvider = selectedValue)"/> | ||||
|             </MudPaper> | ||||
|         </ExpansionPanel> | ||||
|          | ||||
| @ -331,7 +398,7 @@ | ||||
|                 <ConfigurationSelect OptionDescription="Preselect a writing style" Disabled="@(() => !this.SettingsManager.ConfigurationData.RewriteImprove.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedWritingStyle)" Data="@ConfigurationSelectDataFactory.GetWritingStyles4RewriteData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedWritingStyle = selectedValue)" OptionHelp="Which writing style should be preselected?"/> | ||||
|                 <ConfigurationSelect OptionDescription="Preselect a sentence structure" Disabled="@(() => !this.SettingsManager.ConfigurationData.RewriteImprove.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedSentenceStructure)" Data="@ConfigurationSelectDataFactory.GetSentenceStructureData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedSentenceStructure = selectedValue)" OptionHelp="Which voice should be preselected for the sentence structure?"/> | ||||
|                 <ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.RewriteImprove.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.RewriteImprove.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.RewriteImprove.MinimumProviderConfidence = selectedValue)"/> | ||||
|                 <ConfigurationProviderSelection Component="Components.REWRITE_ASSISTANT" Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.RewriteImprove.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedProvider = selectedValue)"/> | ||||
|                 <ConfigurationProviderSelection Component="Components.REWRITE_ASSISTANT" Data="@this.availableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.RewriteImprove.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedProvider = selectedValue)"/> | ||||
|             </MudPaper> | ||||
|         </ExpansionPanel> | ||||
| 
 | ||||
| @ -347,7 +414,7 @@ | ||||
|                 } | ||||
|                 <ConfigurationSelect OptionDescription="Preselect a writing style" Disabled="@(() => !this.SettingsManager.ConfigurationData.EMail.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.EMail.PreselectedWritingStyle)" Data="@ConfigurationSelectDataFactory.GetWritingStyles4EMailData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.EMail.PreselectedWritingStyle = selectedValue)" OptionHelp="Which writing style should be preselected?"/> | ||||
|                 <ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.EMail.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.EMail.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.EMail.MinimumProviderConfidence = selectedValue)"/> | ||||
|                 <ConfigurationProviderSelection Component="Components.EMAIL_ASSISTANT" Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.EMail.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.EMail.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.EMail.PreselectedProvider = selectedValue)"/> | ||||
|                 <ConfigurationProviderSelection Component="Components.EMAIL_ASSISTANT" Data="@this.availableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.EMail.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.EMail.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.EMail.PreselectedProvider = selectedValue)"/> | ||||
|                 <ConfigurationSelect OptionDescription="Preselect one of your profiles?" Disabled="@(() => !this.SettingsManager.ConfigurationData.EMail.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.EMail.PreselectedProfile)" Data="@ConfigurationSelectDataFactory.GetProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.EMail.PreselectedProfile = selectedValue)" OptionHelp="Would you like to preselect one of your profiles?"/> | ||||
|             </MudPaper> | ||||
|         </ExpansionPanel> | ||||
| @ -368,7 +435,7 @@ | ||||
|                     <ConfigurationText OptionDescription="Preselect another target language" Disabled="@(() => !this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions)" Icon="@Icons.Material.Filled.Translate" Text="@(() => this.SettingsManager.ConfigurationData.JobPostings.PreselectOtherLanguage)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.JobPostings.PreselectOtherLanguage = updatedText)"/> | ||||
|                 } | ||||
|                 <ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.JobPostings.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.JobPostings.MinimumProviderConfidence = selectedValue)"/> | ||||
|                 <ConfigurationProviderSelection Component="Components.JOB_POSTING_ASSISTANT" Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.JobPostings.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.JobPostings.PreselectedProvider = selectedValue)"/> | ||||
|                 <ConfigurationProviderSelection Component="Components.JOB_POSTING_ASSISTANT" Data="@this.availableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.JobPostings.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.JobPostings.PreselectedProvider = selectedValue)"/> | ||||
|             </MudPaper> | ||||
|         </ExpansionPanel> | ||||
| 
 | ||||
| @ -379,7 +446,7 @@ | ||||
|                 <ConfigurationOption OptionDescription="Preselect the web content reader?" Disabled="@(() => !this.SettingsManager.ConfigurationData.LegalCheck.PreselectOptions || this.SettingsManager.ConfigurationData.LegalCheck.HideWebContentReader)" LabelOn="Web content reader is preselected" LabelOff="Web content reader is not preselected" State="@(() => this.SettingsManager.ConfigurationData.LegalCheck.PreselectWebContentReader)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.LegalCheck.PreselectWebContentReader = updatedState)" OptionHelp="When enabled, the web content reader is preselected. This is might be useful when you prefer to load legal content from the web very often."/> | ||||
|                 <ConfigurationOption OptionDescription="Preselect the content cleaner agent?" Disabled="@(() => !this.SettingsManager.ConfigurationData.LegalCheck.PreselectOptions || this.SettingsManager.ConfigurationData.LegalCheck.HideWebContentReader)" LabelOn="Content cleaner agent is preselected" LabelOff="Content cleaner agent is not preselected" State="@(() => this.SettingsManager.ConfigurationData.LegalCheck.PreselectContentCleanerAgent)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.LegalCheck.PreselectContentCleanerAgent = updatedState)" OptionHelp="When enabled, the content cleaner agent is preselected. This is might be useful when you prefer to clean up the legal content before translating it."/> | ||||
|                 <ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.LegalCheck.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.LegalCheck.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.LegalCheck.MinimumProviderConfidence = selectedValue)"/> | ||||
|                 <ConfigurationProviderSelection Component="Components.LEGAL_CHECK_ASSISTANT" Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.LegalCheck.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.LegalCheck.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.LegalCheck.PreselectedProvider = selectedValue)"/> | ||||
|                 <ConfigurationProviderSelection Component="Components.LEGAL_CHECK_ASSISTANT" Data="@this.availableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.LegalCheck.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.LegalCheck.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.LegalCheck.PreselectedProvider = selectedValue)"/> | ||||
|                 <ConfigurationSelect OptionDescription="Preselect one of your profiles?" Disabled="@(() => !this.SettingsManager.ConfigurationData.LegalCheck.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.LegalCheck.PreselectedProfile)" Data="@ConfigurationSelectDataFactory.GetProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.LegalCheck.PreselectedProfile = selectedValue)" OptionHelp="Would you like to preselect one of your profiles?"/> | ||||
|             </MudPaper> | ||||
|         </ExpansionPanel> | ||||
| @ -393,7 +460,7 @@ | ||||
|                     <ConfigurationText OptionDescription="Preselect another language" Disabled="@(() => !this.SettingsManager.ConfigurationData.Synonyms.PreselectOptions)" Icon="@Icons.Material.Filled.Translate" Text="@(() => this.SettingsManager.ConfigurationData.Synonyms.PreselectedOtherLanguage)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.Synonyms.PreselectedOtherLanguage = updatedText)"/> | ||||
|                 } | ||||
|                 <ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.Synonyms.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Synonyms.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Synonyms.MinimumProviderConfidence = selectedValue)"/> | ||||
|                 <ConfigurationProviderSelection Component="Components.SYNONYMS_ASSISTANT" Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.Synonyms.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Synonyms.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Synonyms.PreselectedProvider = selectedValue)"/> | ||||
|                 <ConfigurationProviderSelection Component="Components.SYNONYMS_ASSISTANT" Data="@this.availableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.Synonyms.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Synonyms.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Synonyms.PreselectedProvider = selectedValue)"/> | ||||
|             </MudPaper> | ||||
|         </ExpansionPanel> | ||||
| 
 | ||||
| @ -407,7 +474,7 @@ | ||||
|                 } | ||||
|                 <ConfigurationSelect OptionDescription="Preselect one of your profiles?" Disabled="@(() => !this.SettingsManager.ConfigurationData.MyTasks.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.MyTasks.PreselectedProfile)" Data="@ConfigurationSelectDataFactory.GetProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.MyTasks.PreselectedProfile = selectedValue)" OptionHelp="Would you like to preselect one of your profiles?"/> | ||||
|                 <ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.MyTasks.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.MyTasks.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.MyTasks.MinimumProviderConfidence = selectedValue)"/> | ||||
|                 <ConfigurationProviderSelection Component="Components.MY_TASKS_ASSISTANT" Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.MyTasks.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.MyTasks.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.MyTasks.PreselectedProvider = selectedValue)"/> | ||||
|                 <ConfigurationProviderSelection Component="Components.MY_TASKS_ASSISTANT" Data="@this.availableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.MyTasks.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.MyTasks.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.MyTasks.PreselectedProvider = selectedValue)"/> | ||||
|             </MudPaper> | ||||
|         </ExpansionPanel> | ||||
| 
 | ||||
| @ -433,7 +500,7 @@ | ||||
|                 } | ||||
|                 <ConfigurationSelect OptionDescription="Preselect one of your profiles?" Disabled="@(() => !this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectedProfile)" Data="@ConfigurationSelectDataFactory.GetProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectedProfile = selectedValue)" OptionHelp="Would you like to preselect one of your profiles?"/> | ||||
|                 <ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.BiasOfTheDay.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.BiasOfTheDay.MinimumProviderConfidence = selectedValue)"/> | ||||
|                 <ConfigurationProviderSelection Component="Components.BIAS_DAY_ASSISTANT" Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectedProvider = selectedValue)"/> | ||||
|                 <ConfigurationProviderSelection Component="Components.BIAS_DAY_ASSISTANT" Data="@this.availableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectedProvider = selectedValue)"/> | ||||
|             </MudPaper> | ||||
|         </ExpansionPanel> | ||||
|          | ||||
| @ -444,7 +511,7 @@ | ||||
|                     and attempts to convert relative links into absolute links so that they can be used. | ||||
|                 </MudText> | ||||
|                 <ConfigurationOption OptionDescription="Preselect text content cleaner options?" LabelOn="Options are preselected" LabelOff="No options are preselected" State="@(() => this.SettingsManager.ConfigurationData.TextContentCleaner.PreselectAgentOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.TextContentCleaner.PreselectAgentOptions = updatedState)" OptionHelp="When enabled, you can preselect some agent options. This is might be useful when you prefer a LLM."/> | ||||
|                 <ConfigurationProviderSelection Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.TextContentCleaner.PreselectAgentOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.TextContentCleaner.PreselectedAgentProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.TextContentCleaner.PreselectedAgentProvider = selectedValue)"/> | ||||
|                 <ConfigurationProviderSelection Data="@this.availableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.TextContentCleaner.PreselectAgentOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.TextContentCleaner.PreselectedAgentProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.TextContentCleaner.PreselectedAgentProvider = selectedValue)"/> | ||||
|             </MudPaper> | ||||
|         </ExpansionPanel> | ||||
|     </MudExpansionPanels> | ||||
|  | ||||
| @ -22,13 +22,11 @@ public partial class Settings : ComponentBase, IMessageBusReceiver, IDisposable | ||||
|     [Inject] | ||||
|     private MessageBus MessageBus { get; init; } = null!; | ||||
|      | ||||
|     [Inject] | ||||
|     private ILogger<Settings> Logger { get; init; } = null!; | ||||
|      | ||||
|     [Inject] | ||||
|     private RustService RustService { get; init; } = null!; | ||||
|      | ||||
|     private readonly List<ConfigurationSelectData<string>> availableProviders = new(); | ||||
|     private readonly List<ConfigurationSelectData<string>> availableLLMProviders = new(); | ||||
|     private readonly List<ConfigurationSelectData<string>> availableEmbeddingProviders = new(); | ||||
| 
 | ||||
|     #region Overrides of ComponentBase | ||||
| 
 | ||||
| @ -46,14 +44,14 @@ public partial class Settings : ComponentBase, IMessageBusReceiver, IDisposable | ||||
| 
 | ||||
|     #region Provider related | ||||
| 
 | ||||
|     private async Task AddProvider() | ||||
|     private async Task AddLLMProvider() | ||||
|     { | ||||
|         var dialogParameters = new DialogParameters<ProviderDialog> | ||||
|         { | ||||
|             { x => x.IsEditing, false }, | ||||
|         }; | ||||
|          | ||||
|         var dialogReference = await this.DialogService.ShowAsync<ProviderDialog>("Add Provider", dialogParameters, DialogOptions.FULLSCREEN); | ||||
|         var dialogReference = await this.DialogService.ShowAsync<ProviderDialog>("Add LLM Provider", dialogParameters, DialogOptions.FULLSCREEN); | ||||
|         var dialogResult = await dialogReference.Result; | ||||
|         if (dialogResult is null || dialogResult.Canceled) | ||||
|             return; | ||||
| @ -68,7 +66,7 @@ public partial class Settings : ComponentBase, IMessageBusReceiver, IDisposable | ||||
|         await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED); | ||||
|     } | ||||
| 
 | ||||
|     private async Task EditProvider(AIStudio.Settings.Provider provider) | ||||
|     private async Task EditLLMProvider(AIStudio.Settings.Provider provider) | ||||
|     { | ||||
|         var dialogParameters = new DialogParameters<ProviderDialog> | ||||
|         { | ||||
| @ -83,7 +81,7 @@ public partial class Settings : ComponentBase, IMessageBusReceiver, IDisposable | ||||
|             { x => x.DataHost, provider.Host }, | ||||
|         }; | ||||
| 
 | ||||
|         var dialogReference = await this.DialogService.ShowAsync<ProviderDialog>("Edit Provider", dialogParameters, DialogOptions.FULLSCREEN); | ||||
|         var dialogReference = await this.DialogService.ShowAsync<ProviderDialog>("Edit LLM Provider", dialogParameters, DialogOptions.FULLSCREEN); | ||||
|         var dialogResult = await dialogReference.Result; | ||||
|         if (dialogResult is null || dialogResult.Canceled) | ||||
|             return; | ||||
| @ -102,20 +100,19 @@ public partial class Settings : ComponentBase, IMessageBusReceiver, IDisposable | ||||
|         await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED); | ||||
|     } | ||||
| 
 | ||||
|     private async Task DeleteProvider(AIStudio.Settings.Provider provider) | ||||
|     private async Task DeleteLLMProvider(AIStudio.Settings.Provider provider) | ||||
|     { | ||||
|         var dialogParameters = new DialogParameters | ||||
|         { | ||||
|             { "Message", $"Are you sure you want to delete the provider '{provider.InstanceName}'?" }, | ||||
|         }; | ||||
|          | ||||
|         var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Delete Provider", dialogParameters, DialogOptions.FULLSCREEN); | ||||
|         var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Delete LLM Provider", dialogParameters, DialogOptions.FULLSCREEN); | ||||
|         var dialogResult = await dialogReference.Result; | ||||
|         if (dialogResult is null || dialogResult.Canceled) | ||||
|             return; | ||||
|          | ||||
|         var providerInstance = provider.CreateProvider(this.Logger); | ||||
|         var deleteSecretResponse = await this.RustService.DeleteAPIKey(providerInstance); | ||||
|         var deleteSecretResponse = await this.RustService.DeleteAPIKey(provider); | ||||
|         if(deleteSecretResponse.Success) | ||||
|         { | ||||
|             this.SettingsManager.ConfigurationData.Providers.Remove(provider); | ||||
| @ -125,32 +122,8 @@ public partial class Settings : ComponentBase, IMessageBusReceiver, IDisposable | ||||
|         this.UpdateProviders(); | ||||
|         await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED); | ||||
|     } | ||||
|      | ||||
|     private bool HasDashboard(LLMProviders llmProvider) => llmProvider switch | ||||
|     { | ||||
|         LLMProviders.OPEN_AI => true, | ||||
|         LLMProviders.MISTRAL => true, | ||||
|         LLMProviders.ANTHROPIC => true, | ||||
|         LLMProviders.GROQ => true, | ||||
|         LLMProviders.FIREWORKS => true, | ||||
|         LLMProviders.GOOGLE => true, | ||||
|          | ||||
|         _ => false, | ||||
|     }; | ||||
|      | ||||
|     private string GetProviderDashboardURL(LLMProviders llmProvider) => llmProvider switch | ||||
|     { | ||||
|         LLMProviders.OPEN_AI => "https://platform.openai.com/usage", | ||||
|         LLMProviders.MISTRAL => "https://console.mistral.ai/usage/", | ||||
|         LLMProviders.ANTHROPIC => "https://console.anthropic.com/settings/plans", | ||||
|         LLMProviders.GROQ => "https://console.groq.com/settings/usage", | ||||
|         LLMProviders.GOOGLE => "https://console.cloud.google.com/billing", | ||||
|         LLMProviders.FIREWORKS => "https://fireworks.ai/account/billing", | ||||
|          | ||||
|         _ => string.Empty, | ||||
|     }; | ||||
| 
 | ||||
|     private string GetProviderModelName(AIStudio.Settings.Provider provider) | ||||
|     private string GetLLMProviderModelName(AIStudio.Settings.Provider provider) | ||||
|     { | ||||
|         const int MAX_LENGTH = 36; | ||||
|         var modelName = provider.Model.ToString(); | ||||
| @ -159,9 +132,9 @@ public partial class Settings : ComponentBase, IMessageBusReceiver, IDisposable | ||||
|      | ||||
|     private void UpdateProviders() | ||||
|     { | ||||
|         this.availableProviders.Clear(); | ||||
|         this.availableLLMProviders.Clear(); | ||||
|         foreach (var provider in this.SettingsManager.ConfigurationData.Providers) | ||||
|             this.availableProviders.Add(new (provider.InstanceName, provider.Id)); | ||||
|             this.availableLLMProviders.Add(new (provider.InstanceName, provider.Id)); | ||||
|     } | ||||
| 
 | ||||
|     private string GetCurrentConfidenceLevelName(LLMProviders llmProvider) | ||||
| @ -188,6 +161,103 @@ public partial class Settings : ComponentBase, IMessageBusReceiver, IDisposable | ||||
| 
 | ||||
|     #endregion | ||||
| 
 | ||||
|     #region Embedding provider related  | ||||
| 
 | ||||
|     private string GetEmbeddingProviderModelName(EmbeddingProvider provider) | ||||
|     { | ||||
|         const int MAX_LENGTH = 36; | ||||
|         var modelName = provider.Model.ToString(); | ||||
|         return modelName.Length > MAX_LENGTH ? "[...] " + modelName[^Math.Min(MAX_LENGTH, modelName.Length)..] : modelName; | ||||
|     } | ||||
|      | ||||
|     private async Task AddEmbeddingProvider() | ||||
|     { | ||||
|         var dialogParameters = new DialogParameters<EmbeddingDialog> | ||||
|         { | ||||
|             { x => x.IsEditing, false }, | ||||
|         }; | ||||
|          | ||||
|         var dialogReference = await this.DialogService.ShowAsync<EmbeddingDialog>("Add Embedding Provider", dialogParameters, DialogOptions.FULLSCREEN); | ||||
|         var dialogResult = await dialogReference.Result; | ||||
|         if (dialogResult is null || dialogResult.Canceled) | ||||
|             return; | ||||
| 
 | ||||
|         var addedEmbedding = (EmbeddingProvider)dialogResult.Data!; | ||||
|         addedEmbedding = addedEmbedding with { Num = this.SettingsManager.ConfigurationData.NextEmbeddingNum++ }; | ||||
|          | ||||
|         this.SettingsManager.ConfigurationData.EmbeddingProviders.Add(addedEmbedding); | ||||
|         this.UpdateEmbeddingProviders(); | ||||
|          | ||||
|         await this.SettingsManager.StoreSettings(); | ||||
|         await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED); | ||||
|     } | ||||
|      | ||||
|     private async Task EditEmbeddingProvider(EmbeddingProvider embeddingProvider) | ||||
|     { | ||||
|         var dialogParameters = new DialogParameters<EmbeddingDialog> | ||||
|         { | ||||
|             { x => x.DataNum, embeddingProvider.Num }, | ||||
|             { x => x.DataId, embeddingProvider.Id }, | ||||
|             { x => x.DataName, embeddingProvider.Name }, | ||||
|             { x => x.DataLLMProvider, embeddingProvider.UsedLLMProvider }, | ||||
|             { x => x.DataModel, embeddingProvider.Model }, | ||||
|             { x => x.DataHostname, embeddingProvider.Hostname }, | ||||
|             { x => x.IsSelfHosted, embeddingProvider.IsSelfHosted }, | ||||
|             { x => x.IsEditing, true }, | ||||
|             { x => x.DataHost, embeddingProvider.Host }, | ||||
|         }; | ||||
| 
 | ||||
|         var dialogReference = await this.DialogService.ShowAsync<EmbeddingDialog>("Edit Embedding Provider", dialogParameters, DialogOptions.FULLSCREEN); | ||||
|         var dialogResult = await dialogReference.Result; | ||||
|         if (dialogResult is null || dialogResult.Canceled) | ||||
|             return; | ||||
| 
 | ||||
|         var editedEmbeddingProvider = (EmbeddingProvider)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(editedEmbeddingProvider.Num == 0) | ||||
|             editedEmbeddingProvider = editedEmbeddingProvider with { Num = this.SettingsManager.ConfigurationData.NextEmbeddingNum++ }; | ||||
|          | ||||
|         this.SettingsManager.ConfigurationData.EmbeddingProviders[this.SettingsManager.ConfigurationData.EmbeddingProviders.IndexOf(embeddingProvider)] = editedEmbeddingProvider; | ||||
|         this.UpdateEmbeddingProviders(); | ||||
|          | ||||
|         await this.SettingsManager.StoreSettings(); | ||||
|         await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED); | ||||
|     } | ||||
| 
 | ||||
|     private async Task DeleteEmbeddingProvider(EmbeddingProvider provider) | ||||
|     { | ||||
|         var dialogParameters = new DialogParameters | ||||
|         { | ||||
|             { "Message", $"Are you sure you want to delete the embedding provider '{provider.Name}'?" }, | ||||
|         }; | ||||
|          | ||||
|         var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Delete Embedding Provider", dialogParameters, DialogOptions.FULLSCREEN); | ||||
|         var dialogResult = await dialogReference.Result; | ||||
|         if (dialogResult is null || dialogResult.Canceled) | ||||
|             return; | ||||
|          | ||||
|         var deleteSecretResponse = await this.RustService.DeleteAPIKey(provider); | ||||
|         if(deleteSecretResponse.Success) | ||||
|         { | ||||
|             this.SettingsManager.ConfigurationData.EmbeddingProviders.Remove(provider); | ||||
|             await this.SettingsManager.StoreSettings(); | ||||
|         } | ||||
|          | ||||
|         this.UpdateEmbeddingProviders(); | ||||
|         await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED); | ||||
|     } | ||||
|      | ||||
|     private void UpdateEmbeddingProviders() | ||||
|     { | ||||
|         this.availableEmbeddingProviders.Clear(); | ||||
|         foreach (var provider in this.SettingsManager.ConfigurationData.EmbeddingProviders) | ||||
|             this.availableEmbeddingProviders.Add(new (provider.Name, provider.Id)); | ||||
|     } | ||||
| 
 | ||||
|     #endregion | ||||
| 
 | ||||
|     #region Profile related | ||||
| 
 | ||||
|     private async Task AddProfile() | ||||
|  | ||||
| @ -7,7 +7,7 @@ using AIStudio.Provider.OpenAI; | ||||
| 
 | ||||
| namespace AIStudio.Provider.Anthropic; | ||||
| 
 | ||||
| public sealed class ProviderAnthropic(ILogger logger) : BaseProvider("https://api.anthropic.com/v1/", logger), IProvider | ||||
| public sealed class ProviderAnthropic(ILogger logger) : BaseProvider("https://api.anthropic.com/v1/", logger) | ||||
| { | ||||
|     private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new() | ||||
|     { | ||||
| @ -16,12 +16,12 @@ public sealed class ProviderAnthropic(ILogger logger) : BaseProvider("https://ap | ||||
| 
 | ||||
|     #region Implementation of IProvider | ||||
| 
 | ||||
|     public string Id => "Anthropic"; | ||||
|     public override string Id => LLMProviders.ANTHROPIC.ToName(); | ||||
| 
 | ||||
|     public string InstanceName { get; set; } = "Anthropic"; | ||||
|     public override string InstanceName { get; set; } = "Anthropic"; | ||||
| 
 | ||||
|     /// <inheritdoc /> | ||||
|     public async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default) | ||||
|     public override async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default) | ||||
|     { | ||||
|         // Get the API key: | ||||
|         var requestedSecret = await RUST_SERVICE.GetAPIKey(this); | ||||
| @ -136,14 +136,14 @@ public sealed class ProviderAnthropic(ILogger logger) : BaseProvider("https://ap | ||||
| 
 | ||||
|     #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously | ||||
|     /// <inheritdoc /> | ||||
|     public async IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default) | ||||
|     public override async IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default) | ||||
|     { | ||||
|         yield break; | ||||
|     } | ||||
|     #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously | ||||
| 
 | ||||
|     /// <inheritdoc /> | ||||
|     public Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) | ||||
|     public override Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) | ||||
|     { | ||||
|         return Task.FromResult(new[] | ||||
|         { | ||||
| @ -162,13 +162,17 @@ public sealed class ProviderAnthropic(ILogger logger) : BaseProvider("https://ap | ||||
|         }.AsEnumerable()); | ||||
|     } | ||||
| 
 | ||||
|     #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously | ||||
|     /// <inheritdoc /> | ||||
|     public Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default) | ||||
|     public override Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default) | ||||
|     { | ||||
|         return Task.FromResult(Enumerable.Empty<Model>()); | ||||
|     } | ||||
|      | ||||
|     /// <inheritdoc /> | ||||
|     public override Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default) | ||||
|     { | ||||
|         return Task.FromResult(Enumerable.Empty<Model>()); | ||||
|     } | ||||
|     #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously | ||||
| 
 | ||||
|     #endregion | ||||
| } | ||||
| @ -1,3 +1,5 @@ | ||||
| using AIStudio.Chat; | ||||
| 
 | ||||
| using RustService = AIStudio.Tools.RustService; | ||||
| 
 | ||||
| namespace AIStudio.Provider; | ||||
| @ -5,10 +7,10 @@ namespace AIStudio.Provider; | ||||
| /// <summary> | ||||
| /// The base class for all providers. | ||||
| /// </summary> | ||||
| public abstract class BaseProvider | ||||
| public abstract class BaseProvider : IProvider, ISecretId | ||||
| { | ||||
|     /// <summary> | ||||
|     /// The HTTP client to use for all requests. | ||||
|     /// The HTTP client to use it for all requests. | ||||
|     /// </summary> | ||||
|     protected readonly HttpClient httpClient = new(); | ||||
|      | ||||
| @ -39,4 +41,37 @@ public abstract class BaseProvider | ||||
|         // Set the base URL: | ||||
|         this.httpClient.BaseAddress = new(url); | ||||
|     } | ||||
|      | ||||
|     #region Handling of IProvider, which all providers must implement | ||||
|      | ||||
|     /// <inheritdoc /> | ||||
|     public abstract string Id { get; } | ||||
|      | ||||
|     /// <inheritdoc /> | ||||
|     public abstract string InstanceName { get; set; } | ||||
|      | ||||
|     /// <inheritdoc /> | ||||
|     public abstract IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, CancellationToken token = default); | ||||
|      | ||||
|     /// <inheritdoc /> | ||||
|     public abstract IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, CancellationToken token = default); | ||||
|      | ||||
|     /// <inheritdoc /> | ||||
|     public abstract Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default); | ||||
|      | ||||
|     /// <inheritdoc /> | ||||
|     public abstract Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default); | ||||
|      | ||||
|     /// <inheritdoc /> | ||||
|     public abstract Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default); | ||||
|      | ||||
|     #endregion | ||||
|      | ||||
|     #region Implementation of ISecretId | ||||
| 
 | ||||
|     public string SecretId => this.Id; | ||||
| 
 | ||||
|     public string SecretName => this.InstanceName; | ||||
| 
 | ||||
|     #endregion | ||||
| } | ||||
| @ -7,7 +7,7 @@ using AIStudio.Chat; | ||||
| 
 | ||||
| namespace AIStudio.Provider.Fireworks; | ||||
| 
 | ||||
| public class ProviderFireworks(ILogger logger) : BaseProvider("https://api.fireworks.ai/inference/v1/", logger), IProvider | ||||
| public class ProviderFireworks(ILogger logger) : BaseProvider("https://api.fireworks.ai/inference/v1/", logger) | ||||
| { | ||||
|     private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new() | ||||
|     { | ||||
| @ -17,13 +17,13 @@ public class ProviderFireworks(ILogger logger) : BaseProvider("https://api.firew | ||||
|     #region Implementation of IProvider | ||||
| 
 | ||||
|     /// <inheritdoc /> | ||||
|     public string Id => "Fireworks.ai"; | ||||
|     public override string Id => LLMProviders.FIREWORKS.ToName(); | ||||
| 
 | ||||
|     /// <inheritdoc /> | ||||
|     public string InstanceName { get; set; } = "Fireworks.ai"; | ||||
|     public override string InstanceName { get; set; } = "Fireworks.ai"; | ||||
| 
 | ||||
|     /// <inheritdoc /> | ||||
|     public async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default) | ||||
|     public override async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default) | ||||
|     { | ||||
|         // Get the API key: | ||||
|         var requestedSecret = await RUST_SERVICE.GetAPIKey(this); | ||||
| @ -138,20 +138,26 @@ public class ProviderFireworks(ILogger logger) : BaseProvider("https://api.firew | ||||
| 
 | ||||
|     #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously | ||||
|     /// <inheritdoc /> | ||||
|     public async IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default) | ||||
|     public override async IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default) | ||||
|     { | ||||
|         yield break; | ||||
|     } | ||||
|     #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously | ||||
| 
 | ||||
|     /// <inheritdoc /> | ||||
|     public Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) | ||||
|     public override Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) | ||||
|     { | ||||
|         return Task.FromResult(Enumerable.Empty<Model>()); | ||||
|     } | ||||
| 
 | ||||
|     /// <inheritdoc /> | ||||
|     public Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default) | ||||
|     public override Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default) | ||||
|     { | ||||
|         return Task.FromResult(Enumerable.Empty<Model>()); | ||||
|     } | ||||
|      | ||||
|     /// <inheritdoc /> | ||||
|     public override Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default) | ||||
|     { | ||||
|         return Task.FromResult(Enumerable.Empty<Model>()); | ||||
|     } | ||||
|  | ||||
| @ -8,7 +8,7 @@ using AIStudio.Provider.OpenAI; | ||||
| 
 | ||||
| namespace AIStudio.Provider.Google; | ||||
| 
 | ||||
| public class ProviderGoogle(ILogger logger) : BaseProvider("https://generativelanguage.googleapis.com/v1beta/", logger), IProvider | ||||
| public class ProviderGoogle(ILogger logger) : BaseProvider("https://generativelanguage.googleapis.com/v1beta/", logger) | ||||
| { | ||||
|     private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new() | ||||
|     { | ||||
| @ -18,13 +18,13 @@ public class ProviderGoogle(ILogger logger) : BaseProvider("https://generativela | ||||
|     #region Implementation of IProvider | ||||
| 
 | ||||
|     /// <inheritdoc /> | ||||
|     public string Id => "Google"; | ||||
|     public override string Id => LLMProviders.GOOGLE.ToName(); | ||||
| 
 | ||||
|     /// <inheritdoc /> | ||||
|     public string InstanceName { get; set; } = "Google Gemini"; | ||||
|     public override string InstanceName { get; set; } = "Google Gemini"; | ||||
| 
 | ||||
|     /// <inheritdoc /> | ||||
|     public async IAsyncEnumerable<string> StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default) | ||||
|     public override async IAsyncEnumerable<string> StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default) | ||||
|     { | ||||
|         // Get the API key: | ||||
|         var requestedSecret = await RUST_SERVICE.GetAPIKey(this); | ||||
| @ -139,27 +139,44 @@ public class ProviderGoogle(ILogger logger) : BaseProvider("https://generativela | ||||
| 
 | ||||
|     #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously | ||||
|     /// <inheritdoc /> | ||||
|     public async IAsyncEnumerable<ImageURL> StreamImageCompletion(Provider.Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default) | ||||
|     public override async IAsyncEnumerable<ImageURL> StreamImageCompletion(Provider.Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default) | ||||
|     { | ||||
|         yield break; | ||||
|     } | ||||
|     #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously | ||||
| 
 | ||||
|     /// <inheritdoc /> | ||||
|     public Task<IEnumerable<Provider.Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) | ||||
|     public override async Task<IEnumerable<Provider.Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) | ||||
|     { | ||||
|         return this.LoadModels(token, apiKeyProvisional); | ||||
|         var modelResponse = await this.LoadModels(token, apiKeyProvisional); | ||||
|         if(modelResponse == default) | ||||
|             return []; | ||||
|          | ||||
|         return modelResponse.Models.Where(model => | ||||
|                 model.Name.StartsWith("models/gemini-", StringComparison.InvariantCultureIgnoreCase)) | ||||
|             .Select(n => new Provider.Model(n.Name.Replace("models/", string.Empty), n.DisplayName)); | ||||
|     } | ||||
| 
 | ||||
|     /// <inheritdoc /> | ||||
|     public Task<IEnumerable<Provider.Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default) | ||||
|     public override Task<IEnumerable<Provider.Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default) | ||||
|     { | ||||
|         return Task.FromResult(Enumerable.Empty<Provider.Model>()); | ||||
|     } | ||||
| 
 | ||||
|     public override async Task<IEnumerable<Provider.Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default) | ||||
|     { | ||||
|         var modelResponse = await this.LoadModels(token, apiKeyProvisional); | ||||
|         if(modelResponse == default) | ||||
|             return []; | ||||
|          | ||||
|         return modelResponse.Models.Where(model => | ||||
|                 model.Name.StartsWith("models/text-embedding-", StringComparison.InvariantCultureIgnoreCase)) | ||||
|             .Select(n => new Provider.Model(n.Name.Replace("models/", string.Empty), n.DisplayName)); | ||||
|     } | ||||
| 
 | ||||
|     #endregion | ||||
| 
 | ||||
|     private async Task<IEnumerable<Provider.Model>> LoadModels(CancellationToken token, string? apiKeyProvisional = null) | ||||
|     private async Task<ModelsResponse> LoadModels(CancellationToken token, string? apiKeyProvisional = null) | ||||
|     { | ||||
|         var secretKey = apiKeyProvisional switch | ||||
|         { | ||||
| @ -170,19 +187,17 @@ public class ProviderGoogle(ILogger logger) : BaseProvider("https://generativela | ||||
|                 _ => null, | ||||
|             } | ||||
|         }; | ||||
|          | ||||
| 
 | ||||
|         if (secretKey is null) | ||||
|             return []; | ||||
|             return default; | ||||
| 
 | ||||
|         var request = new HttpRequestMessage(HttpMethod.Get, $"models?key={secretKey}"); | ||||
|         var response = await this.httpClient.SendAsync(request, token); | ||||
|          | ||||
|         if(!response.IsSuccessStatusCode) | ||||
|             return []; | ||||
|             return default; | ||||
| 
 | ||||
|         var modelResponse = await response.Content.ReadFromJsonAsync<ModelsResponse>(token); | ||||
|         return modelResponse.Models.Where(model => | ||||
|             model.Name.StartsWith("models/gemini-", StringComparison.InvariantCultureIgnoreCase)) | ||||
|             .Select(n => new Provider.Model(n.Name.Replace("models/", string.Empty), n.DisplayName)); | ||||
|         return modelResponse; | ||||
|     } | ||||
| } | ||||
| @ -8,7 +8,7 @@ using AIStudio.Provider.OpenAI; | ||||
| 
 | ||||
| namespace AIStudio.Provider.Groq; | ||||
| 
 | ||||
| public class ProviderGroq(ILogger logger) : BaseProvider("https://api.groq.com/openai/v1/", logger), IProvider | ||||
| public class ProviderGroq(ILogger logger) : BaseProvider("https://api.groq.com/openai/v1/", logger) | ||||
| { | ||||
|     private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new() | ||||
|     { | ||||
| @ -18,13 +18,13 @@ public class ProviderGroq(ILogger logger) : BaseProvider("https://api.groq.com/o | ||||
|     #region Implementation of IProvider | ||||
| 
 | ||||
|     /// <inheritdoc /> | ||||
|     public string Id => "Groq"; | ||||
|     public override string Id => LLMProviders.GROQ.ToName(); | ||||
| 
 | ||||
|     /// <inheritdoc /> | ||||
|     public string InstanceName { get; set; } = "Groq"; | ||||
|     public override string InstanceName { get; set; } = "Groq"; | ||||
| 
 | ||||
|     /// <inheritdoc /> | ||||
|     public async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default) | ||||
|     public override async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default) | ||||
|     { | ||||
|         // Get the API key: | ||||
|         var requestedSecret = await RUST_SERVICE.GetAPIKey(this); | ||||
| @ -141,23 +141,29 @@ public class ProviderGroq(ILogger logger) : BaseProvider("https://api.groq.com/o | ||||
| 
 | ||||
|     #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously | ||||
|     /// <inheritdoc /> | ||||
|     public async IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default) | ||||
|     public override async IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default) | ||||
|     { | ||||
|         yield break; | ||||
|     } | ||||
|     #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously | ||||
| 
 | ||||
|     /// <inheritdoc /> | ||||
|     public Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) | ||||
|     public override Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) | ||||
|     { | ||||
|         return this.LoadModels(token, apiKeyProvisional); | ||||
|     } | ||||
| 
 | ||||
|     /// <inheritdoc /> | ||||
|     public Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default) | ||||
|     public override Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default) | ||||
|     { | ||||
|         return Task.FromResult<IEnumerable<Model>>(Array.Empty<Model>()); | ||||
|     } | ||||
|      | ||||
|     /// <inheritdoc /> | ||||
|     public override Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default) | ||||
|     { | ||||
|         return Task.FromResult(Enumerable.Empty<Model>()); | ||||
|     } | ||||
| 
 | ||||
|     #endregion | ||||
| 
 | ||||
|  | ||||
| @ -16,7 +16,7 @@ public interface IProvider | ||||
|     /// 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 string InstanceName { get; } | ||||
|      | ||||
|     /// <summary> | ||||
|     /// Starts a chat completion stream. | ||||
| @ -53,4 +53,12 @@ public interface IProvider | ||||
|     /// <param name="token">The cancellation token.</param> | ||||
|     /// <returns>The list of image models.</returns> | ||||
|     public Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default); | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// Load all possible embedding models that can be used with this provider. | ||||
|     /// </summary> | ||||
|     /// <param name="apiKeyProvisional">The provisional API key to use. Useful when the user is adding a new provider. When null, the stored API key is used.</param> | ||||
|     /// <param name="token">The cancellation token.</param> | ||||
|     /// <returns>The list of embedding models.</returns> | ||||
|     public Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default); | ||||
| } | ||||
| @ -7,6 +7,8 @@ using AIStudio.Provider.OpenAI; | ||||
| using AIStudio.Provider.SelfHosted; | ||||
| using AIStudio.Settings; | ||||
| 
 | ||||
| using Host = AIStudio.Provider.SelfHosted.Host; | ||||
| 
 | ||||
| namespace AIStudio.Provider; | ||||
| 
 | ||||
| public static class LLMProvidersExtensions | ||||
| @ -89,7 +91,7 @@ public static class LLMProvidersExtensions | ||||
|         // | ||||
|         // Self-hosted providers are treated as a special case anyway. | ||||
|         // | ||||
|         LLMProviders.SELF_HOSTED => false, | ||||
|         LLMProviders.SELF_HOSTED => true, | ||||
|          | ||||
|         _ => false, | ||||
|     }; | ||||
| @ -101,20 +103,36 @@ public static class LLMProvidersExtensions | ||||
|     /// <param name="logger">The logger to use.</param> | ||||
|     /// <returns>The provider instance.</returns> | ||||
|     public static IProvider CreateProvider(this Settings.Provider providerSettings, ILogger logger) | ||||
|     { | ||||
|         return providerSettings.UsedLLMProvider.CreateProvider(providerSettings.InstanceName, providerSettings.Host, providerSettings.Hostname, logger); | ||||
|     } | ||||
|      | ||||
|     /// <summary> | ||||
|     /// Creates a new provider instance based on the embedding provider value. | ||||
|     /// </summary> | ||||
|     /// <param name="embeddingProviderSettings">The embedding provider settings.</param> | ||||
|     /// <param name="logger">The logger to use.</param> | ||||
|     /// <returns>The provider instance.</returns> | ||||
|     public static IProvider CreateProvider(this EmbeddingProvider embeddingProviderSettings, ILogger logger) | ||||
|     { | ||||
|         return embeddingProviderSettings.UsedLLMProvider.CreateProvider(embeddingProviderSettings.Name, embeddingProviderSettings.Host, embeddingProviderSettings.Hostname, logger); | ||||
|     } | ||||
|      | ||||
|     private static IProvider CreateProvider(this LLMProviders provider, string instanceName, Host host, string hostname, ILogger logger) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             return providerSettings.UsedLLMProvider switch | ||||
|             return provider switch | ||||
|             { | ||||
|                 LLMProviders.OPEN_AI => new ProviderOpenAI(logger) { InstanceName = providerSettings.InstanceName }, | ||||
|                 LLMProviders.ANTHROPIC => new ProviderAnthropic(logger) { InstanceName = providerSettings.InstanceName }, | ||||
|                 LLMProviders.MISTRAL => new ProviderMistral(logger) { InstanceName = providerSettings.InstanceName }, | ||||
|                 LLMProviders.GOOGLE => new ProviderGoogle(logger) { InstanceName = providerSettings.InstanceName }, | ||||
|                 LLMProviders.OPEN_AI => new ProviderOpenAI(logger) { InstanceName = instanceName }, | ||||
|                 LLMProviders.ANTHROPIC => new ProviderAnthropic(logger) { InstanceName = instanceName }, | ||||
|                 LLMProviders.MISTRAL => new ProviderMistral(logger) { InstanceName = instanceName }, | ||||
|                 LLMProviders.GOOGLE => new ProviderGoogle(logger) { InstanceName = instanceName }, | ||||
|                  | ||||
|                 LLMProviders.GROQ => new ProviderGroq(logger) { InstanceName = providerSettings.InstanceName }, | ||||
|                 LLMProviders.FIREWORKS => new ProviderFireworks(logger) { InstanceName = providerSettings.InstanceName }, | ||||
|                 LLMProviders.GROQ => new ProviderGroq(logger) { InstanceName = instanceName }, | ||||
|                 LLMProviders.FIREWORKS => new ProviderFireworks(logger) { InstanceName = instanceName }, | ||||
|                  | ||||
|                 LLMProviders.SELF_HOSTED => new ProviderSelfHosted(logger, providerSettings) { InstanceName = providerSettings.InstanceName }, | ||||
|                 LLMProviders.SELF_HOSTED => new ProviderSelfHosted(logger, host, hostname) { InstanceName = instanceName }, | ||||
|                  | ||||
|                 _ => new NoProvider(), | ||||
|             }; | ||||
| @ -125,4 +143,125 @@ public static class LLMProvidersExtensions | ||||
|             return new NoProvider(); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     public static string GetCreationURL(this LLMProviders provider) => provider switch | ||||
|     { | ||||
|         LLMProviders.OPEN_AI => "https://platform.openai.com/signup", | ||||
|         LLMProviders.MISTRAL => "https://console.mistral.ai/", | ||||
|         LLMProviders.ANTHROPIC => "https://console.anthropic.com/dashboard", | ||||
|         LLMProviders.GOOGLE => "https://console.cloud.google.com/", | ||||
|       | ||||
|         LLMProviders.GROQ => "https://console.groq.com/", | ||||
|         LLMProviders.FIREWORKS => "https://fireworks.ai/login", | ||||
|          | ||||
|         _ => string.Empty, | ||||
|     }; | ||||
| 
 | ||||
|     public static string GetDashboardURL(this LLMProviders provider) => provider switch | ||||
|     { | ||||
|         LLMProviders.OPEN_AI => "https://platform.openai.com/usage", | ||||
|         LLMProviders.MISTRAL => "https://console.mistral.ai/usage/", | ||||
|         LLMProviders.ANTHROPIC => "https://console.anthropic.com/settings/plans", | ||||
|         LLMProviders.GROQ => "https://console.groq.com/settings/usage", | ||||
|         LLMProviders.GOOGLE => "https://console.cloud.google.com/billing", | ||||
|         LLMProviders.FIREWORKS => "https://fireworks.ai/account/billing", | ||||
|          | ||||
|         _ => string.Empty, | ||||
|     }; | ||||
| 
 | ||||
|     public static bool HasDashboard(this LLMProviders provider) => provider switch | ||||
|     { | ||||
|         LLMProviders.OPEN_AI => true, | ||||
|         LLMProviders.MISTRAL => true, | ||||
|         LLMProviders.ANTHROPIC => true, | ||||
|         LLMProviders.GROQ => true, | ||||
|         LLMProviders.FIREWORKS => true, | ||||
|         LLMProviders.GOOGLE => true, | ||||
|          | ||||
|         _ => false, | ||||
|     }; | ||||
| 
 | ||||
|     public static string GetModelsOverviewURL(this LLMProviders provider) => provider switch | ||||
|     { | ||||
|         LLMProviders.FIREWORKS => "https://fireworks.ai/models?show=Serverless", | ||||
|         _ => string.Empty, | ||||
|     }; | ||||
| 
 | ||||
|     public static bool IsLLMModelProvidedManually(this LLMProviders provider) => provider switch | ||||
|     { | ||||
|         LLMProviders.FIREWORKS => true, | ||||
|         _ => false, | ||||
|     }; | ||||
|      | ||||
|     public static bool IsEmbeddingModelProvidedManually(this LLMProviders provider, Host host) => provider switch | ||||
|     { | ||||
|         LLMProviders.SELF_HOSTED => host is not Host.LM_STUDIO, | ||||
|         _ => false, | ||||
|     }; | ||||
| 
 | ||||
|     public static bool IsHostNeeded(this LLMProviders provider) => provider switch | ||||
|     { | ||||
|         LLMProviders.SELF_HOSTED => true, | ||||
|         _ => false, | ||||
|     }; | ||||
| 
 | ||||
|     public static bool IsHostnameNeeded(this LLMProviders provider) => provider switch | ||||
|     { | ||||
|         LLMProviders.SELF_HOSTED => true, | ||||
|         _ => false, | ||||
|     }; | ||||
| 
 | ||||
|     public static bool IsAPIKeyNeeded(this LLMProviders provider, Host host) => provider switch | ||||
|     { | ||||
|         LLMProviders.OPEN_AI => true, | ||||
|         LLMProviders.MISTRAL => true, | ||||
|         LLMProviders.ANTHROPIC => true, | ||||
|         LLMProviders.GOOGLE => true, | ||||
|          | ||||
|         LLMProviders.GROQ => true, | ||||
|         LLMProviders.FIREWORKS => true, | ||||
|          | ||||
|         LLMProviders.SELF_HOSTED => host is Host.OLLAMA, | ||||
|          | ||||
|         _ => false, | ||||
|     }; | ||||
| 
 | ||||
|     public static bool ShowRegisterButton(this LLMProviders provider) => provider switch | ||||
|     { | ||||
|         LLMProviders.OPEN_AI => true, | ||||
|         LLMProviders.MISTRAL => true, | ||||
|         LLMProviders.ANTHROPIC => true, | ||||
|         LLMProviders.GOOGLE => true, | ||||
|          | ||||
|         LLMProviders.GROQ => true, | ||||
|         LLMProviders.FIREWORKS => true, | ||||
|          | ||||
|         _ => false, | ||||
|     }; | ||||
| 
 | ||||
|     public static bool CanLoadModels(this LLMProviders provider, Host host, string? apiKey) | ||||
|     { | ||||
|         if (provider is LLMProviders.SELF_HOSTED) | ||||
|         { | ||||
|             switch (host) | ||||
|             { | ||||
|                 case Host.NONE: | ||||
|                 case Host.LLAMACPP: | ||||
|                 default: | ||||
|                     return false; | ||||
| 
 | ||||
|                 case Host.OLLAMA: | ||||
|                 case Host.LM_STUDIO: | ||||
|                     return true; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if(provider is LLMProviders.NONE) | ||||
|             return false; | ||||
|          | ||||
|         if(string.IsNullOrWhiteSpace(apiKey)) | ||||
|             return false; | ||||
|          | ||||
|         return true; | ||||
|     } | ||||
| } | ||||
| @ -8,7 +8,7 @@ using AIStudio.Provider.OpenAI; | ||||
| 
 | ||||
| namespace AIStudio.Provider.Mistral; | ||||
| 
 | ||||
| public sealed class ProviderMistral(ILogger logger) : BaseProvider("https://api.mistral.ai/v1/", logger), IProvider | ||||
| public sealed class ProviderMistral(ILogger logger) : BaseProvider("https://api.mistral.ai/v1/", logger) | ||||
| { | ||||
|     private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new() | ||||
|     { | ||||
| @ -17,12 +17,12 @@ public sealed class ProviderMistral(ILogger logger) : BaseProvider("https://api. | ||||
|      | ||||
|     #region Implementation of IProvider | ||||
| 
 | ||||
|     public string Id => "Mistral"; | ||||
|     public override string Id => LLMProviders.MISTRAL.ToName(); | ||||
|      | ||||
|     public string InstanceName { get; set; } = "Mistral"; | ||||
|     public override string InstanceName { get; set; } = "Mistral"; | ||||
| 
 | ||||
|     /// <inheritdoc /> | ||||
|     public async IAsyncEnumerable<string> StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default) | ||||
|     public override async IAsyncEnumerable<string> StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default) | ||||
|     { | ||||
|         // Get the API key: | ||||
|         var requestedSecret = await RUST_SERVICE.GetAPIKey(this); | ||||
| @ -140,14 +140,45 @@ public sealed class ProviderMistral(ILogger logger) : BaseProvider("https://api. | ||||
| 
 | ||||
|     #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously | ||||
|     /// <inheritdoc /> | ||||
|     public async IAsyncEnumerable<ImageURL> StreamImageCompletion(Provider.Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default) | ||||
|     public override async IAsyncEnumerable<ImageURL> StreamImageCompletion(Provider.Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default) | ||||
|     { | ||||
|         yield break; | ||||
|     } | ||||
|     #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously | ||||
| 
 | ||||
|     /// <inheritdoc /> | ||||
|     public async Task<IEnumerable<Provider.Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) | ||||
|     public override async Task<IEnumerable<Provider.Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) | ||||
|     { | ||||
|         var modelResponse = await this.LoadModelList(apiKeyProvisional, token); | ||||
|         if(modelResponse == default) | ||||
|             return []; | ||||
|          | ||||
|         return modelResponse.Data.Where(n =>  | ||||
|             !n.Id.StartsWith("code", StringComparison.InvariantCulture) && | ||||
|             !n.Id.Contains("embed", StringComparison.InvariantCulture)) | ||||
|             .Select(n => new Provider.Model(n.Id, null)); | ||||
|     } | ||||
|      | ||||
|     /// <inheritdoc /> | ||||
|     public override async Task<IEnumerable<Provider.Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default) | ||||
|     { | ||||
|         var modelResponse = await this.LoadModelList(apiKeyProvisional, token); | ||||
|         if(modelResponse == default) | ||||
|             return []; | ||||
|          | ||||
|         return modelResponse.Data.Where(n => n.Id.Contains("embed", StringComparison.InvariantCulture)) | ||||
|             .Select(n => new Provider.Model(n.Id, null)); | ||||
|     } | ||||
|      | ||||
|     /// <inheritdoc /> | ||||
|     public override Task<IEnumerable<Provider.Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default) | ||||
|     { | ||||
|         return Task.FromResult(Enumerable.Empty<Provider.Model>()); | ||||
|     } | ||||
| 
 | ||||
|     #endregion | ||||
|      | ||||
|     private async Task<ModelsResponse> LoadModelList(string? apiKeyProvisional, CancellationToken token) | ||||
|     { | ||||
|         var secretKey = apiKeyProvisional switch | ||||
|         { | ||||
| @ -160,29 +191,16 @@ public sealed class ProviderMistral(ILogger logger) : BaseProvider("https://api. | ||||
|         }; | ||||
| 
 | ||||
|         if (secretKey is null) | ||||
|             return []; | ||||
|             return default; | ||||
|          | ||||
|         var request = new HttpRequestMessage(HttpMethod.Get, "models"); | ||||
|         request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", secretKey); | ||||
| 
 | ||||
|         var response = await this.httpClient.SendAsync(request, token); | ||||
|         if(!response.IsSuccessStatusCode) | ||||
|             return []; | ||||
|             return default; | ||||
| 
 | ||||
|         var modelResponse = await response.Content.ReadFromJsonAsync<ModelsResponse>(token); | ||||
|         return modelResponse.Data.Where(n =>  | ||||
|             !n.Id.StartsWith("code", StringComparison.InvariantCulture) && | ||||
|             !n.Id.Contains("embed", StringComparison.InvariantCulture)) | ||||
|             .Select(n => new Provider.Model(n.Id, null)); | ||||
|         return modelResponse; | ||||
|     } | ||||
| 
 | ||||
|     #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously | ||||
|     /// <inheritdoc /> | ||||
|     public Task<IEnumerable<Provider.Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default) | ||||
|     { | ||||
|         return Task.FromResult(Enumerable.Empty<Provider.Model>()); | ||||
|     } | ||||
|     #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously | ||||
| 
 | ||||
|     #endregion | ||||
| } | ||||
| @ -15,6 +15,8 @@ public class NoProvider : IProvider | ||||
|     public Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) => Task.FromResult<IEnumerable<Model>>([]); | ||||
| 
 | ||||
|     public Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default) => Task.FromResult<IEnumerable<Model>>([]); | ||||
|      | ||||
|     public Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default) => Task.FromResult<IEnumerable<Model>>([]); | ||||
| 
 | ||||
|     public async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatChatThread, [EnumeratorCancellation] CancellationToken token = default) | ||||
|     { | ||||
|  | ||||
| @ -10,7 +10,7 @@ namespace AIStudio.Provider.OpenAI; | ||||
| /// <summary> | ||||
| /// The OpenAI provider. | ||||
| /// </summary> | ||||
| public sealed class ProviderOpenAI(ILogger logger) : BaseProvider("https://api.openai.com/v1/", logger), IProvider | ||||
| public sealed class ProviderOpenAI(ILogger logger) : BaseProvider("https://api.openai.com/v1/", logger) | ||||
| { | ||||
|     private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new() | ||||
|     { | ||||
| @ -20,13 +20,13 @@ public sealed class ProviderOpenAI(ILogger logger) : BaseProvider("https://api.o | ||||
|     #region Implementation of IProvider | ||||
| 
 | ||||
|     /// <inheritdoc /> | ||||
|     public string Id => "OpenAI"; | ||||
|     public override string Id => LLMProviders.OPEN_AI.ToName(); | ||||
| 
 | ||||
|     /// <inheritdoc /> | ||||
|     public string InstanceName { get; set; } = "OpenAI"; | ||||
|     public override string InstanceName { get; set; } = "OpenAI"; | ||||
| 
 | ||||
|     /// <inheritdoc /> | ||||
|     public async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default) | ||||
|     public override async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default) | ||||
|     { | ||||
|         // Get the API key: | ||||
|         var requestedSecret = await RUST_SERVICE.GetAPIKey(this); | ||||
| @ -144,23 +144,29 @@ public sealed class ProviderOpenAI(ILogger logger) : BaseProvider("https://api.o | ||||
| 
 | ||||
|     #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously | ||||
|     /// <inheritdoc /> | ||||
|     public async IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default) | ||||
|     public override async IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default) | ||||
|     { | ||||
|         yield break; | ||||
|     } | ||||
|     #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously | ||||
| 
 | ||||
|     /// <inheritdoc /> | ||||
|     public Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) | ||||
|     public override Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) | ||||
|     { | ||||
|         return this.LoadModels(["gpt-", "o1-"], token, apiKeyProvisional); | ||||
|     } | ||||
| 
 | ||||
|     /// <inheritdoc /> | ||||
|     public Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default) | ||||
|     public override Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default) | ||||
|     { | ||||
|         return this.LoadModels(["dall-e-"], token, apiKeyProvisional); | ||||
|     } | ||||
|      | ||||
|     /// <inheritdoc /> | ||||
|     public override Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default) | ||||
|     { | ||||
|         return this.LoadModels(["text-embedding-"], token, apiKeyProvisional); | ||||
|     } | ||||
| 
 | ||||
|     #endregion | ||||
| 
 | ||||
|  | ||||
| @ -7,36 +7,4 @@ public enum Host | ||||
|     LM_STUDIO, | ||||
|     LLAMACPP, | ||||
|     OLLAMA, | ||||
| } | ||||
| 
 | ||||
| public static class HostExtensions | ||||
| { | ||||
|     public static string Name(this Host host) => host switch | ||||
|     { | ||||
|         Host.NONE => "None", | ||||
| 
 | ||||
|         Host.LM_STUDIO => "LM Studio", | ||||
|         Host.LLAMACPP => "llama.cpp", | ||||
|         Host.OLLAMA => "ollama", | ||||
| 
 | ||||
|         _ => "Unknown", | ||||
|     }; | ||||
| 
 | ||||
|     public static string BaseURL(this Host host) => host switch | ||||
|     { | ||||
|         Host.LM_STUDIO => "/v1/", | ||||
|         Host.LLAMACPP => "/v1/", | ||||
|         Host.OLLAMA => "/v1/", | ||||
| 
 | ||||
|         _ => "/v1/", | ||||
|     }; | ||||
| 
 | ||||
|     public static string ChatURL(this Host host) => host switch | ||||
|     { | ||||
|         Host.LM_STUDIO => "chat/completions", | ||||
|         Host.LLAMACPP => "chat/completions", | ||||
|         Host.OLLAMA => "chat/completions", | ||||
| 
 | ||||
|         _ => "chat/completions", | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										47
									
								
								app/MindWork AI Studio/Provider/SelfHosted/HostExtensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								app/MindWork AI Studio/Provider/SelfHosted/HostExtensions.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | ||||
| namespace AIStudio.Provider.SelfHosted; | ||||
| 
 | ||||
| public static class HostExtensions | ||||
| { | ||||
|     public static string Name(this Host host) => host switch | ||||
|     { | ||||
|         Host.NONE => "None", | ||||
| 
 | ||||
|         Host.LM_STUDIO => "LM Studio", | ||||
|         Host.LLAMACPP => "llama.cpp", | ||||
|         Host.OLLAMA => "ollama", | ||||
| 
 | ||||
|         _ => "Unknown", | ||||
|     }; | ||||
| 
 | ||||
|     public static string BaseURL(this Host host) => host switch | ||||
|     { | ||||
|         Host.LM_STUDIO => "/v1/", | ||||
|         Host.LLAMACPP => "/v1/", | ||||
|         Host.OLLAMA => "/v1/", | ||||
| 
 | ||||
|         _ => "/v1/", | ||||
|     }; | ||||
| 
 | ||||
|     public static string ChatURL(this Host host) => host switch | ||||
|     { | ||||
|         Host.LM_STUDIO => "chat/completions", | ||||
|         Host.LLAMACPP => "chat/completions", | ||||
|         Host.OLLAMA => "chat/completions", | ||||
| 
 | ||||
|         _ => "chat/completions", | ||||
|     }; | ||||
|      | ||||
|     public static bool AreEmbeddingsSupported(this Host host) | ||||
|     { | ||||
|         switch (host) | ||||
|         { | ||||
|             case Host.LM_STUDIO: | ||||
|             case Host.OLLAMA: | ||||
|                 return true; | ||||
|              | ||||
|             default: | ||||
|             case Host.LLAMACPP: | ||||
|                 return false; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -8,7 +8,7 @@ using AIStudio.Provider.OpenAI; | ||||
| 
 | ||||
| namespace AIStudio.Provider.SelfHosted; | ||||
| 
 | ||||
| public sealed class ProviderSelfHosted(ILogger logger, Settings.Provider provider) : BaseProvider($"{provider.Hostname}{provider.Host.BaseURL()}", logger), IProvider | ||||
| public sealed class ProviderSelfHosted(ILogger logger, Host host, string hostname) : BaseProvider($"{hostname}{host.BaseURL()}", logger) | ||||
| { | ||||
|     private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new() | ||||
|     { | ||||
| @ -17,12 +17,12 @@ public sealed class ProviderSelfHosted(ILogger logger, Settings.Provider provide | ||||
| 
 | ||||
|     #region Implementation of IProvider | ||||
| 
 | ||||
|     public string Id => "Self-hosted"; | ||||
|     public override string Id => LLMProviders.SELF_HOSTED.ToName(); | ||||
|      | ||||
|     public string InstanceName { get; set; } = "Self-hosted"; | ||||
|     public override string InstanceName { get; set; } = "Self-hosted"; | ||||
|      | ||||
|     /// <inheritdoc /> | ||||
|     public async IAsyncEnumerable<string> StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default) | ||||
|     public override async IAsyncEnumerable<string> StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default) | ||||
|     { | ||||
|         // Get the API key: | ||||
|         var requestedSecret = await RUST_SERVICE.GetAPIKey(this, isTrying: true); | ||||
| @ -70,7 +70,7 @@ public sealed class ProviderSelfHosted(ILogger logger, Settings.Provider provide | ||||
|         try | ||||
|         { | ||||
|             // Build the HTTP post request: | ||||
|             var request = new HttpRequestMessage(HttpMethod.Post, provider.Host.ChatURL()); | ||||
|             var request = new HttpRequestMessage(HttpMethod.Post, host.ChatURL()); | ||||
| 
 | ||||
|             // Set the authorization header: | ||||
|             if (requestedSecret.Success) | ||||
| @ -148,18 +148,18 @@ public sealed class ProviderSelfHosted(ILogger logger, Settings.Provider provide | ||||
| 
 | ||||
|     #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously | ||||
|     /// <inheritdoc /> | ||||
|     public async IAsyncEnumerable<ImageURL> StreamImageCompletion(Provider.Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default) | ||||
|     public override async IAsyncEnumerable<ImageURL> StreamImageCompletion(Provider.Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default) | ||||
|     { | ||||
|         yield break; | ||||
|     } | ||||
|     #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously | ||||
| 
 | ||||
| 
 | ||||
|     public async Task<IEnumerable<Provider.Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) | ||||
|     public override async Task<IEnumerable<Provider.Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             switch (provider.Host) | ||||
|             switch (host) | ||||
|             { | ||||
|                 case Host.LLAMACPP: | ||||
|                     // Right now, llama.cpp only supports one model. | ||||
| @ -168,27 +168,7 @@ public sealed class ProviderSelfHosted(ILogger logger, Settings.Provider provide | ||||
|              | ||||
|                 case Host.LM_STUDIO: | ||||
|                 case Host.OLLAMA: | ||||
|                      | ||||
|                     var secretKey = apiKeyProvisional switch | ||||
|                     { | ||||
|                         not null => apiKeyProvisional, | ||||
|                         _ => await RUST_SERVICE.GetAPIKey(this, isTrying: true) switch | ||||
|                         { | ||||
|                             { Success: true } result => await result.Secret.Decrypt(ENCRYPTION), | ||||
|                             _ => null, | ||||
|                         } | ||||
|                     }; | ||||
|                      | ||||
|                     var lmStudioRequest = new HttpRequestMessage(HttpMethod.Get, "models"); | ||||
|                     if(secretKey is not null) | ||||
|                         lmStudioRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKeyProvisional); | ||||
|                      | ||||
|                     var lmStudioResponse = await this.httpClient.SendAsync(lmStudioRequest, token); | ||||
|                     if(!lmStudioResponse.IsSuccessStatusCode) | ||||
|                         return []; | ||||
| 
 | ||||
|                     var lmStudioModelResponse = await lmStudioResponse.Content.ReadFromJsonAsync<ModelsResponse>(token); | ||||
|                     return lmStudioModelResponse.Data.Select(n => new Provider.Model(n.Id, null)); | ||||
|                     return await this.LoadModels(["embed"], [], token, apiKeyProvisional); | ||||
|             } | ||||
| 
 | ||||
|             return []; | ||||
| @ -200,13 +180,58 @@ public sealed class ProviderSelfHosted(ILogger logger, Settings.Provider provide | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously | ||||
|     /// <inheritdoc /> | ||||
|     public Task<IEnumerable<Provider.Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default) | ||||
|     public override Task<IEnumerable<Provider.Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default) | ||||
|     { | ||||
|         return Task.FromResult(Enumerable.Empty<Provider.Model>()); | ||||
|     } | ||||
|     #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously | ||||
| 
 | ||||
|     public override async Task<IEnumerable<Provider.Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             switch (host) | ||||
|             { | ||||
|                 case Host.LM_STUDIO: | ||||
|                 case Host.OLLAMA: | ||||
|                     return await this.LoadModels([], ["embed"], token, apiKeyProvisional); | ||||
|             } | ||||
| 
 | ||||
|             return []; | ||||
|         } | ||||
|         catch(Exception e) | ||||
|         { | ||||
|             this.logger.LogError($"Failed to load text models from self-hosted provider: {e.Message}"); | ||||
|             return []; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     #endregion | ||||
| 
 | ||||
|     private async Task<IEnumerable<Provider.Model>> LoadModels(string[] ignorePhrases, string[] filterPhrases, CancellationToken token, string? apiKeyProvisional = null) | ||||
|     { | ||||
|         var secretKey = apiKeyProvisional switch | ||||
|         { | ||||
|             not null => apiKeyProvisional, | ||||
|             _ => await RUST_SERVICE.GetAPIKey(this, isTrying: true) switch | ||||
|             { | ||||
|                 { Success: true } result => await result.Secret.Decrypt(ENCRYPTION), | ||||
|                 _ => null, | ||||
|             } | ||||
|         }; | ||||
|                      | ||||
|         var lmStudioRequest = new HttpRequestMessage(HttpMethod.Get, "models"); | ||||
|         if(secretKey is not null) | ||||
|             lmStudioRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKeyProvisional); | ||||
|                      | ||||
|         var lmStudioResponse = await this.httpClient.SendAsync(lmStudioRequest, token); | ||||
|         if(!lmStudioResponse.IsSuccessStatusCode) | ||||
|             return []; | ||||
| 
 | ||||
|         var lmStudioModelResponse = await lmStudioResponse.Content.ReadFromJsonAsync<ModelsResponse>(token); | ||||
|         return lmStudioModelResponse.Data. | ||||
|             Where(model => !ignorePhrases.Any(ignorePhrase => model.Id.Contains(ignorePhrase, StringComparison.InvariantCulture)) && | ||||
|                            filterPhrases.All( filter => model.Id.Contains(filter, StringComparison.InvariantCulture))) | ||||
|             .Select(n => new Provider.Model(n.Id, null)); | ||||
|     } | ||||
| } | ||||
| @ -83,8 +83,9 @@ public static class ConfigurationSelectDataFactory | ||||
|         yield return new("All preview features are hidden", PreviewVisibility.NONE); | ||||
|         yield return new("Also show features ready for release; these should be stable", PreviewVisibility.RELEASE_CANDIDATE); | ||||
|         yield return new("Also show features in beta: these are almost ready for release; expect some bugs", PreviewVisibility.BETA); | ||||
|         yield return new("Also show features in alpha: these are in early development; expect bugs and missing features", PreviewVisibility.ALPHA); | ||||
|         yield return new("Also show features in alpha: these are in development; expect bugs and missing features", PreviewVisibility.ALPHA); | ||||
|         yield return new("Show also prototype features: these are works in progress; expect bugs and missing features", PreviewVisibility.PROTOTYPE); | ||||
|         yield return new("Show also experimental features: these are experimental; expect bugs, missing features, many changes", PreviewVisibility.EXPERIMENTAL); | ||||
|     } | ||||
|      | ||||
|     public static IEnumerable<ConfigurationSelectData<NavBehavior>> GetNavBehaviorData() | ||||
|  | ||||
| @ -20,6 +20,11 @@ public sealed class Data | ||||
|     /// Settings concerning the LLM providers. | ||||
|     /// </summary> | ||||
|     public DataLLMProviders LLMProviders { get; init; } = new(); | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// A collection of embedding providers configured. | ||||
|     /// </summary> | ||||
|     public List<EmbeddingProvider> EmbeddingProviders { get; init; } = []; | ||||
|      | ||||
|     /// <summary> | ||||
|     /// List of configured profiles. | ||||
| @ -31,6 +36,11 @@ public sealed class Data | ||||
|     /// </summary> | ||||
|     public uint NextProviderNum { get; set; } = 1; | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// The next embedding number to use. | ||||
|     /// </summary> | ||||
|     public uint NextEmbeddingNum { get; set; } = 1; | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// The next profile number to use. | ||||
|     /// </summary> | ||||
|  | ||||
| @ -8,4 +8,5 @@ public enum PreviewVisibility | ||||
|     BETA, | ||||
|     ALPHA, | ||||
|     PROTOTYPE, | ||||
|     EXPERIMENTAL, | ||||
| } | ||||
							
								
								
									
										32
									
								
								app/MindWork AI Studio/Settings/EmbeddingProvider.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								app/MindWork AI Studio/Settings/EmbeddingProvider.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | ||||
| using System.Text.Json.Serialization; | ||||
| 
 | ||||
| using AIStudio.Provider; | ||||
| 
 | ||||
| using Host = AIStudio.Provider.SelfHosted.Host; | ||||
| 
 | ||||
| namespace AIStudio.Settings; | ||||
| 
 | ||||
| public readonly record struct EmbeddingProvider( | ||||
|     uint Num, | ||||
|     string Id, | ||||
|     string Name, | ||||
|     LLMProviders UsedLLMProvider, | ||||
|     Model Model, | ||||
|     bool IsSelfHosted = false, | ||||
|     string Hostname = "http://localhost:1234", | ||||
|     Host Host = Host.NONE) : ISecretId | ||||
| { | ||||
|     public override string ToString() => this.Name; | ||||
|      | ||||
|     #region Implementation of ISecretId | ||||
|      | ||||
|     /// <inheritdoc /> | ||||
|     [JsonIgnore] | ||||
|     public string SecretId => this.Id; | ||||
|      | ||||
|     /// <inheritdoc /> | ||||
|     [JsonIgnore] | ||||
|     public string SecretName => this.Name; | ||||
|      | ||||
|     #endregion | ||||
| } | ||||
| @ -1,3 +1,5 @@ | ||||
| using System.Text.Json.Serialization; | ||||
| 
 | ||||
| using AIStudio.Provider; | ||||
| 
 | ||||
| using Host = AIStudio.Provider.SelfHosted.Host; | ||||
| @ -22,7 +24,7 @@ public readonly record struct Provider( | ||||
|     Model Model, | ||||
|     bool IsSelfHosted = false, | ||||
|     string Hostname = "http://localhost:1234", | ||||
|     Host Host = Host.NONE) | ||||
|     Host Host = Host.NONE) : ISecretId | ||||
| { | ||||
|     #region Overrides of ValueType | ||||
| 
 | ||||
| @ -40,4 +42,16 @@ public readonly record struct Provider( | ||||
|     } | ||||
| 
 | ||||
|     #endregion | ||||
|      | ||||
|     #region Implementation of ISecretId | ||||
|      | ||||
|     /// <inheritdoc /> | ||||
|     [JsonIgnore] | ||||
|     public string SecretId => this.Id; | ||||
|      | ||||
|     /// <inheritdoc /> | ||||
|     [JsonIgnore] | ||||
|     public string SecretName => this.InstanceName; | ||||
|      | ||||
|     #endregion | ||||
| } | ||||
							
								
								
									
										17
									
								
								app/MindWork AI Studio/Tools/ISecretId.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								app/MindWork AI Studio/Tools/ISecretId.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| namespace AIStudio.Tools; | ||||
| 
 | ||||
| /// <summary> | ||||
| /// Represents an interface defining a secret identifier. | ||||
| /// </summary> | ||||
| public interface ISecretId | ||||
| { | ||||
|     /// <summary> | ||||
|     /// The unique ID of the secret. | ||||
|     /// </summary> | ||||
|     public string SecretId { get; } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// The instance name of the secret. | ||||
|     /// </summary> | ||||
|     public string SecretName { get; } | ||||
| } | ||||
| @ -1,7 +1,6 @@ | ||||
| using System.Security.Cryptography; | ||||
| using System.Text.Json; | ||||
| 
 | ||||
| using AIStudio.Provider; | ||||
| using AIStudio.Tools.Rust; | ||||
| 
 | ||||
| // ReSharper disable NotAccessedPositionalProperty.Local | ||||
| @ -255,71 +254,71 @@ public sealed class RustService : IDisposable | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// Try to get the API key for the given provider. | ||||
|     /// Try to get the API key for the given secret ID. | ||||
|     /// </summary> | ||||
|     /// <param name="provider">The provider to get the API key for.</param> | ||||
|     /// <param name="secretId">The secret ID to get the API key for.</param> | ||||
|     /// <param name="isTrying">Indicates if we are trying to get the API key. In that case, we don't log errors.</param> | ||||
|     /// <returns>The requested secret.</returns> | ||||
|     public async Task<RequestedSecret> GetAPIKey(IProvider provider, bool isTrying = false) | ||||
|     public async Task<RequestedSecret> GetAPIKey(ISecretId secretId, bool isTrying = false) | ||||
|     { | ||||
|         var secretRequest = new SelectSecretRequest($"provider::{provider.Id}::{provider.InstanceName}::api_key", Environment.UserName, isTrying); | ||||
|         var secretRequest = new SelectSecretRequest($"provider::{secretId.SecretId}::{secretId.SecretName}::api_key", Environment.UserName, isTrying); | ||||
|         var result = await this.http.PostAsJsonAsync("/secrets/get", secretRequest, this.jsonRustSerializerOptions); | ||||
|         if (!result.IsSuccessStatusCode) | ||||
|         { | ||||
|             if(!isTrying) | ||||
|                 this.logger!.LogError($"Failed to get the API key for provider '{provider.Id}' due to an API issue: '{result.StatusCode}'"); | ||||
|                 this.logger!.LogError($"Failed to get the API key for secret ID '{secretId.SecretId}' due to an API issue: '{result.StatusCode}'"); | ||||
|             return new RequestedSecret(false, new EncryptedText(string.Empty), "Failed to get the API key due to an API issue."); | ||||
|         } | ||||
|          | ||||
|         var secret = await result.Content.ReadFromJsonAsync<RequestedSecret>(this.jsonRustSerializerOptions); | ||||
|         if (!secret.Success && !isTrying) | ||||
|             this.logger!.LogError($"Failed to get the API key for provider '{provider.Id}': '{secret.Issue}'"); | ||||
|             this.logger!.LogError($"Failed to get the API key for secret ID '{secretId.SecretId}': '{secret.Issue}'"); | ||||
|          | ||||
|         return secret; | ||||
|     } | ||||
|      | ||||
|     /// <summary> | ||||
|     /// Try to store the API key for the given provider. | ||||
|     /// Try to store the API key for the given secret ID. | ||||
|     /// </summary> | ||||
|     /// <param name="provider">The provider to store the API key for.</param> | ||||
|     /// <param name="secretId">The secret ID 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(IProvider provider, string key) | ||||
|     public async Task<StoreSecretResponse> SetAPIKey(ISecretId secretId, string key) | ||||
|     { | ||||
|         var encryptedKey = await this.encryptor!.Encrypt(key); | ||||
|         var request = new StoreSecretRequest($"provider::{provider.Id}::{provider.InstanceName}::api_key", Environment.UserName, encryptedKey); | ||||
|         var request = new StoreSecretRequest($"provider::{secretId.SecretId}::{secretId.SecretName}::api_key", Environment.UserName, encryptedKey); | ||||
|         var result = await this.http.PostAsJsonAsync("/secrets/store", request, this.jsonRustSerializerOptions); | ||||
|         if (!result.IsSuccessStatusCode) | ||||
|         { | ||||
|             this.logger!.LogError($"Failed to store the API key for provider '{provider.Id}' due to an API issue: '{result.StatusCode}'"); | ||||
|             this.logger!.LogError($"Failed to store the API key for secret ID '{secretId.SecretId}' due to an API issue: '{result.StatusCode}'"); | ||||
|             return new StoreSecretResponse(false, "Failed to get the API key due to an API issue."); | ||||
|         } | ||||
|          | ||||
|         var state = await result.Content.ReadFromJsonAsync<StoreSecretResponse>(this.jsonRustSerializerOptions); | ||||
|         if (!state.Success) | ||||
|             this.logger!.LogError($"Failed to store the API key for provider '{provider.Id}': '{state.Issue}'"); | ||||
|             this.logger!.LogError($"Failed to store the API key for secret ID '{secretId.SecretId}': '{state.Issue}'"); | ||||
|          | ||||
|         return state; | ||||
|     } | ||||
|      | ||||
|     /// <summary> | ||||
|     /// Tries to delete the API key for the given provider. | ||||
|     /// Tries to delete the API key for the given secret ID. | ||||
|     /// </summary> | ||||
|     /// <param name="provider">The provider to delete the API key for.</param> | ||||
|     /// <param name="secretId">The secret ID to delete the API key for.</param> | ||||
|     /// <returns>The delete secret response.</returns> | ||||
|     public async Task<DeleteSecretResponse> DeleteAPIKey(IProvider provider) | ||||
|     public async Task<DeleteSecretResponse> DeleteAPIKey(ISecretId secretId) | ||||
|     { | ||||
|         var request = new SelectSecretRequest($"provider::{provider.Id}::{provider.InstanceName}::api_key", Environment.UserName, false); | ||||
|         var request = new SelectSecretRequest($"provider::{secretId.SecretId}::{secretId.SecretName}::api_key", Environment.UserName, false); | ||||
|         var result = await this.http.PostAsJsonAsync("/secrets/delete", request, this.jsonRustSerializerOptions); | ||||
|         if (!result.IsSuccessStatusCode) | ||||
|         { | ||||
|             this.logger!.LogError($"Failed to delete the API key for provider '{provider.Id}' due to an API issue: '{result.StatusCode}'"); | ||||
|             this.logger!.LogError($"Failed to delete the API key for secret ID '{secretId.SecretId}' due to an API issue: '{result.StatusCode}'"); | ||||
|             return new DeleteSecretResponse{Success = false, WasEntryFound = false, Issue = "Failed to delete the API key due to an API issue."}; | ||||
|         } | ||||
|          | ||||
|         var state = await result.Content.ReadFromJsonAsync<DeleteSecretResponse>(this.jsonRustSerializerOptions); | ||||
|         if (!state.Success) | ||||
|             this.logger!.LogError($"Failed to delete the API key for provider '{provider.Id}': '{state.Issue}'"); | ||||
|             this.logger!.LogError($"Failed to delete the API key for secret ID '{secretId.SecretId}': '{state.Issue}'"); | ||||
|          | ||||
|         return state; | ||||
|     } | ||||
|  | ||||
| @ -0,0 +1,96 @@ | ||||
| using AIStudio.Provider; | ||||
| 
 | ||||
| using Host = AIStudio.Provider.SelfHosted.Host; | ||||
| 
 | ||||
| namespace AIStudio.Tools.Validation; | ||||
| 
 | ||||
| public sealed class ProviderValidation | ||||
| { | ||||
|     public Func<LLMProviders> GetProvider { get; init; } = () => LLMProviders.NONE; | ||||
|      | ||||
|     public Func<string> GetAPIKeyStorageIssue { get; init; } = () => string.Empty; | ||||
|      | ||||
|     public Func<string> GetPreviousInstanceName { get; init; } = () => string.Empty; | ||||
|      | ||||
|     public Func<IEnumerable<string>> GetUsedInstanceNames { get; init; } = () => []; | ||||
| 
 | ||||
|     public Func<Host> GetHost { get; init; } = () => Host.NONE; | ||||
|      | ||||
|     public string? ValidatingHostname(string hostname) | ||||
|     { | ||||
|         if(this.GetProvider() != LLMProviders.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://"; | ||||
| 
 | ||||
|         if(!Uri.TryCreate(hostname, UriKind.Absolute, out _)) | ||||
|             return "The hostname is not a valid HTTP(S) URL."; | ||||
|          | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     public string? ValidatingAPIKey(string apiKey) | ||||
|     { | ||||
|         if(this.GetProvider() is LLMProviders.SELF_HOSTED) | ||||
|             return null; | ||||
|          | ||||
|         var apiKeyStorageIssue = this.GetAPIKeyStorageIssue(); | ||||
|         if(!string.IsNullOrWhiteSpace(apiKeyStorageIssue)) | ||||
|             return apiKeyStorageIssue; | ||||
| 
 | ||||
|         if(string.IsNullOrWhiteSpace(apiKey)) | ||||
|             return "Please enter an API key."; | ||||
|          | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     public string? ValidatingInstanceName(string instanceName) | ||||
|     { | ||||
|         if (string.IsNullOrWhiteSpace(instanceName)) | ||||
|             return "Please enter an instance name."; | ||||
|          | ||||
|         if (instanceName.Length > 40) | ||||
|             return "The instance name must not exceed 40 characters."; | ||||
|          | ||||
|         // The instance name must be unique: | ||||
|         var lowerInstanceName = instanceName.ToLowerInvariant(); | ||||
|         if (lowerInstanceName != this.GetPreviousInstanceName() && this.GetUsedInstanceNames().Contains(lowerInstanceName)) | ||||
|             return "The instance name must be unique; the chosen name is already in use."; | ||||
|          | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     public string? ValidatingModel(Model model) | ||||
|     { | ||||
|         if(this.GetProvider() is LLMProviders.SELF_HOSTED && this.GetHost() == Host.LLAMACPP) | ||||
|             return null; | ||||
|          | ||||
|         if (model == default) | ||||
|             return "Please select a model."; | ||||
|          | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     public string? ValidatingProvider(LLMProviders llmProvider) | ||||
|     { | ||||
|         if (llmProvider == LLMProviders.NONE) | ||||
|             return "Please select a provider."; | ||||
|          | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     public string? ValidatingHost(Host host) | ||||
|     { | ||||
|         if(this.GetProvider() is not LLMProviders.SELF_HOSTED) | ||||
|             return null; | ||||
| 
 | ||||
|         if (host == Host.NONE) | ||||
|             return "Please select a host."; | ||||
| 
 | ||||
|         return null; | ||||
|     } | ||||
| } | ||||
| @ -1,2 +1,4 @@ | ||||
| # v0.9.22, build 197 (2024-1x-xx xx:xx UTC) | ||||
| - Added the possibility to configure preview feature visibility in the app settings. This is useful for users who want to test new features before they are officially released. | ||||
| - Added the possibility to configure preview feature visibility in the app settings. This is useful for users who want to test new features before they are officially released. | ||||
| - Added the possibility to configure embedding providers in the app settings. Embeddings are necessary in order to integrate local data and files. | ||||
| - Improved self-hosted LLM provider configuration by filtering embedding models. | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user