diff --git a/app/MindWork AI Studio/Components/Pages/Settings.razor b/app/MindWork AI Studio/Components/Pages/Settings.razor index c8b827be..2becc03b 100644 --- a/app/MindWork AI Studio/Components/Pages/Settings.razor +++ b/app/MindWork AI Studio/Components/Pages/Settings.razor @@ -1,5 +1,6 @@ @page "/settings" @using AIStudio.Provider +@using Host = AIStudio.Provider.SelfHosted.Host Settings @@ -26,10 +27,18 @@ @context.InstanceName @context.UsedProvider - @if(context.UsedProvider is not Providers.SELF_HOSTED) + @if (context.UsedProvider is not Providers.SELF_HOSTED) + { @context.Model + } + else if (context.UsedProvider is Providers.SELF_HOSTED && context.Host is not Host.LLAMACPP) + { + @context.Model + } else + { @("as selected by provider") + } diff --git a/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs b/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs index 63fb1051..7790d2e8 100644 --- a/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs +++ b/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs @@ -33,7 +33,7 @@ public sealed class ProviderSelfHosted(Settings.Provider provider) : BaseProvide // Prepare the OpenAI HTTP chat request: var providerChatRequest = JsonSerializer.Serialize(new ChatRequest { - Model = (await this.GetTextModels(jsRuntime, settings, token: token)).First().Id, + Model = chatModel.Id, // Build the messages: // - First of all the system prompt @@ -137,17 +137,33 @@ public sealed class ProviderSelfHosted(Settings.Provider provider) : BaseProvide public async Task> GetTextModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default) { - var request = new HttpRequestMessage(HttpMethod.Get, "models"); - var response = await this.httpClient.SendAsync(request, token); - if(!response.IsSuccessStatusCode) - return []; + try + { + switch (provider.Host) + { + case Host.LLAMACPP: + // Right now, llama.cpp only supports one model. + // There is no API to list the model(s). + return [ new Provider.Model("as configured by llama.cpp") ]; + + case Host.LM_STUDIO: + case Host.OLLAMA: + var lmStudioRequest = new HttpRequestMessage(HttpMethod.Get, "models"); + var lmStudioResponse = await this.httpClient.SendAsync(lmStudioRequest, token); + if(!lmStudioResponse.IsSuccessStatusCode) + return []; - var modelResponse = await response.Content.ReadFromJsonAsync(token); - if (modelResponse.Data.Length > 1) - Console.WriteLine("Warning: multiple models found; using the first one."); - - var firstModel = modelResponse.Data.First(); - return [ new Provider.Model(firstModel.Id) ]; + var lmStudioModelResponse = await lmStudioResponse.Content.ReadFromJsonAsync(token); + return lmStudioModelResponse.Data.Select(n => new Provider.Model(n.Id)); + } + + return []; + } + catch(Exception e) + { + Console.WriteLine($"Failed to load text models from self-hosted provider: {e.Message}"); + return []; + } } #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously diff --git a/app/MindWork AI Studio/Settings/ProviderDialog.razor b/app/MindWork AI Studio/Settings/ProviderDialog.razor index 3646764d..e83740e6 100644 --- a/app/MindWork AI Studio/Settings/ProviderDialog.razor +++ b/app/MindWork AI Studio/Settings/ProviderDialog.razor @@ -51,8 +51,8 @@ - Load - + Load + @foreach (var model in this.availableModels) { @model diff --git a/app/MindWork AI Studio/Settings/ProviderDialog.razor.cs b/app/MindWork AI Studio/Settings/ProviderDialog.razor.cs index efcff584..edd00e91 100644 --- a/app/MindWork AI Studio/Settings/ProviderDialog.razor.cs +++ b/app/MindWork AI Studio/Settings/ProviderDialog.razor.cs @@ -122,10 +122,11 @@ public partial class ProviderDialog : ComponentBase this.dataEditingPreviousInstanceName = this.DataInstanceName.ToLowerInvariant(); // - // We cannot load the API key nor models for self-hosted providers: + // We cannot load the API key for self-hosted providers: // if (this.DataProvider is Providers.SELF_HOSTED) { + await this.ReloadModels(); await base.OnInitializedAsync(); return; } @@ -182,19 +183,21 @@ public partial class ProviderDialog : ComponentBase // Use the data model to store the provider. // We just return this data to the parent component: var addedProviderSettings = this.CreateProviderSettings(); - - // We need to instantiate the provider to store the API key: - var provider = addedProviderSettings.CreateProvider(); - - // Store the API key in the OS secure storage: - var storeResponse = await this.SettingsManager.SetAPIKey(this.JsRuntime, provider, this.dataAPIKey); - if (!storeResponse.Success) + if (addedProviderSettings.UsedProvider != Providers.SELF_HOSTED) { - 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; + // We need to instantiate the provider to store the API key: + var provider = addedProviderSettings.CreateProvider(); + + // Store the API key in the OS secure storage: + var storeResponse = await this.SettingsManager.SetAPIKey(this.JsRuntime, provider, 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)); } @@ -219,7 +222,7 @@ public partial class ProviderDialog : ComponentBase private string? ValidatingModel(Model model) { - if(this.DataProvider is Providers.SELF_HOSTED) + if(this.DataProvider is Providers.SELF_HOSTED && this.DataHost == Host.LLAMACPP) return null; if (model == default) @@ -319,11 +322,43 @@ public partial class ProviderDialog : ComponentBase this.availableModels.AddRange(orderedModels); } - private bool CanLoadModels => !string.IsNullOrWhiteSpace(this.dataAPIKey) && this.DataProvider != Providers.NONE && this.DataProvider != Providers.SELF_HOSTED; - + private bool CanLoadModels() + { + if (this.DataProvider is Providers.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.DataProvider is Providers.NONE) + return false; + + if(string.IsNullOrWhiteSpace(this.dataAPIKey)) + return false; + + return true; + } + private bool IsCloudProvider => this.DataProvider is not Providers.SELF_HOSTED; private bool IsSelfHostedOrNone => this.DataProvider is Providers.SELF_HOSTED or Providers.NONE; + + private bool IsNoneProvider => this.DataProvider is Providers.NONE; private string GetProviderCreationURL() => this.DataProvider switch {