diff --git a/app/MindWork AI Studio.sln.DotSettings b/app/MindWork AI Studio.sln.DotSettings index ac41b88..74de15b 100644 --- a/app/MindWork AI Studio.sln.DotSettings +++ b/app/MindWork AI Studio.sln.DotSettings @@ -1,4 +1,6 @@  AI + LM MSG + True True \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/AssistantBase.razor.cs b/app/MindWork AI Studio/Components/AssistantBase.razor.cs index c298861..7fcbd37 100644 --- a/app/MindWork AI Studio/Components/AssistantBase.razor.cs +++ b/app/MindWork AI Studio/Components/AssistantBase.razor.cs @@ -26,8 +26,8 @@ public abstract partial class AssistantBase : ComponentBase private protected virtual RenderFragment? Body => null; protected static readonly Dictionary USER_INPUT_ATTRIBUTES = new(); - - protected AIStudio.Settings.Provider selectedProvider; + + protected AIStudio.Settings.Provider providerSettings; protected MudForm? form; protected bool inputIsValid; @@ -109,6 +109,6 @@ public abstract partial class AssistantBase : ComponentBase // Use the selected provider to get the AI response. // By awaiting this line, we wait for the entire // content to be streamed. - await aiText.CreateFromProviderAsync(this.selectedProvider.UsedProvider.CreateProvider(this.selectedProvider.InstanceName, this.selectedProvider.Hostname), this.JsRuntime, this.SettingsManager, this.selectedProvider.Model, this.chatThread); + await aiText.CreateFromProviderAsync(this.providerSettings.CreateProvider(), this.JsRuntime, this.SettingsManager, this.providerSettings.Model, this.chatThread); } } \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/Pages/Chat.razor b/app/MindWork AI Studio/Components/Pages/Chat.razor index edd8551..d56106a 100644 --- a/app/MindWork AI Studio/Components/Pages/Chat.razor +++ b/app/MindWork AI Studio/Components/Pages/Chat.razor @@ -15,7 +15,7 @@ } - + @foreach (var provider in this.SettingsManager.ConfigurationData.Providers) { diff --git a/app/MindWork AI Studio/Components/Pages/Chat.razor.cs b/app/MindWork AI Studio/Components/Pages/Chat.razor.cs index 6707637..09dd708 100644 --- a/app/MindWork AI Studio/Components/Pages/Chat.razor.cs +++ b/app/MindWork AI Studio/Components/Pages/Chat.razor.cs @@ -32,7 +32,7 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable private const Placement TOOLBAR_TOOLTIP_PLACEMENT = Placement.Bottom; private static readonly Dictionary USER_INPUT_ATTRIBUTES = new(); - private AIStudio.Settings.Provider selectedProvider; + private AIStudio.Settings.Provider providerSettings; private ChatThread? chatThread; private bool hasUnsavedChanges; private bool isStreaming; @@ -61,11 +61,11 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable #endregion - private bool IsProviderSelected => this.selectedProvider.UsedProvider != Providers.NONE; + private bool IsProviderSelected => this.providerSettings.UsedProvider != Providers.NONE; private string ProviderPlaceholder => this.IsProviderSelected ? "Type your input here..." : "Select a provider first"; - private string InputLabel => this.IsProviderSelected ? $"Your Prompt (use selected instance '{this.selectedProvider.InstanceName}', provider '{this.selectedProvider.UsedProvider.ToName()}')" : "Select a provider first"; + private string InputLabel => this.IsProviderSelected ? $"Your Prompt (use selected instance '{this.providerSettings.InstanceName}', provider '{this.providerSettings.UsedProvider.ToName()}')" : "Select a provider first"; private bool CanThreadBeSaved => this.chatThread is not null && this.chatThread.Blocks.Count > 0; @@ -151,7 +151,7 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable // Use the selected provider to get the AI response. // By awaiting this line, we wait for the entire // content to be streamed. - await aiText.CreateFromProviderAsync(this.selectedProvider.UsedProvider.CreateProvider(this.selectedProvider.InstanceName, this.selectedProvider.Hostname), this.JsRuntime, this.SettingsManager, this.selectedProvider.Model, this.chatThread); + await aiText.CreateFromProviderAsync(this.providerSettings.CreateProvider(), this.JsRuntime, this.SettingsManager, this.providerSettings.Model, this.chatThread); // Save the chat: if (this.SettingsManager.ConfigurationData.WorkspaceStorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY) diff --git a/app/MindWork AI Studio/Components/Pages/IconFinder/AssistantIconFinder.razor b/app/MindWork AI Studio/Components/Pages/IconFinder/AssistantIconFinder.razor index e3d7b54..02a5993 100644 --- a/app/MindWork AI Studio/Components/Pages/IconFinder/AssistantIconFinder.razor +++ b/app/MindWork AI Studio/Components/Pages/IconFinder/AssistantIconFinder.razor @@ -17,7 +17,7 @@ } - + @foreach (var provider in this.SettingsManager.ConfigurationData.Providers) { diff --git a/app/MindWork AI Studio/Components/Pages/Settings.razor b/app/MindWork AI Studio/Components/Pages/Settings.razor index c8b827b..2becc03 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/Components/Pages/Settings.razor.cs b/app/MindWork AI Studio/Components/Pages/Settings.razor.cs index e6df748..23c14d3 100644 --- a/app/MindWork AI Studio/Components/Pages/Settings.razor.cs +++ b/app/MindWork AI Studio/Components/Pages/Settings.razor.cs @@ -53,6 +53,7 @@ public partial class Settings : ComponentBase { x => x.DataHostname, provider.Hostname }, { x => x.IsSelfHosted, provider.IsSelfHosted }, { x => x.IsEditing, true }, + { x => x.DataHost, provider.Host }, }; var dialogReference = await this.DialogService.ShowAsync("Edit Provider", dialogParameters, DialogOptions.FULLSCREEN); @@ -83,7 +84,7 @@ public partial class Settings : ComponentBase if (dialogResult.Canceled) return; - var providerInstance = provider.UsedProvider.CreateProvider(provider.InstanceName, provider.Hostname); + var providerInstance = provider.CreateProvider(); var deleteSecretResponse = await this.SettingsManager.DeleteAPIKey(this.JsRuntime, providerInstance); if(deleteSecretResponse.Success) { diff --git a/app/MindWork AI Studio/Components/Pages/TextSummarizer/AssistantTextSummarizer.razor b/app/MindWork AI Studio/Components/Pages/TextSummarizer/AssistantTextSummarizer.razor index 3f6ff48..d290541 100644 --- a/app/MindWork AI Studio/Components/Pages/TextSummarizer/AssistantTextSummarizer.razor +++ b/app/MindWork AI Studio/Components/Pages/TextSummarizer/AssistantTextSummarizer.razor @@ -31,7 +31,7 @@ } - + @foreach (var provider in this.SettingsManager.ConfigurationData.Providers) { diff --git a/app/MindWork AI Studio/Components/Pages/Translator/AssistantTranslator.razor b/app/MindWork AI Studio/Components/Pages/Translator/AssistantTranslator.razor index b167317..ac06d21 100644 --- a/app/MindWork AI Studio/Components/Pages/Translator/AssistantTranslator.razor +++ b/app/MindWork AI Studio/Components/Pages/Translator/AssistantTranslator.razor @@ -38,7 +38,7 @@ else } - + @foreach (var provider in this.SettingsManager.ConfigurationData.Providers) { diff --git a/app/MindWork AI Studio/Provider/Providers.cs b/app/MindWork AI Studio/Provider/Providers.cs index db99cc3..3f0d88e 100644 --- a/app/MindWork AI Studio/Provider/Providers.cs +++ b/app/MindWork AI Studio/Provider/Providers.cs @@ -45,17 +45,15 @@ public static class ExtensionsProvider /// /// Creates a new provider instance based on the provider value. /// - /// The provider value. - /// The used instance name. - /// The hostname of the provider. + /// The provider settings. /// The provider instance. - public static IProvider CreateProvider(this Providers provider, string instanceName, string hostname = "http://localhost:1234") => provider switch + public static IProvider CreateProvider(this Settings.Provider providerSettings) => providerSettings.UsedProvider switch { - Providers.OPEN_AI => new ProviderOpenAI { InstanceName = instanceName }, - Providers.ANTHROPIC => new ProviderAnthropic { InstanceName = instanceName }, - Providers.MISTRAL => new ProviderMistral { InstanceName = instanceName }, + Providers.OPEN_AI => new ProviderOpenAI { InstanceName = providerSettings.InstanceName }, + Providers.ANTHROPIC => new ProviderAnthropic { InstanceName = providerSettings.InstanceName }, + Providers.MISTRAL => new ProviderMistral { InstanceName = providerSettings.InstanceName }, - Providers.SELF_HOSTED => new ProviderSelfHosted(hostname) { InstanceName = instanceName }, + Providers.SELF_HOSTED => new ProviderSelfHosted(providerSettings) { InstanceName = providerSettings.InstanceName }, _ => new NoProvider(), }; diff --git a/app/MindWork AI Studio/Provider/SelfHosted/Host.cs b/app/MindWork AI Studio/Provider/SelfHosted/Host.cs new file mode 100644 index 0000000..0e3a26d --- /dev/null +++ b/app/MindWork AI Studio/Provider/SelfHosted/Host.cs @@ -0,0 +1,42 @@ +namespace AIStudio.Provider.SelfHosted; + +public enum Host +{ + NONE, + + 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", + }; +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs b/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs index 4093224..7790d2e 100644 --- a/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs +++ b/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs @@ -8,7 +8,7 @@ using AIStudio.Settings; namespace AIStudio.Provider.SelfHosted; -public sealed class ProviderSelfHosted(string hostname) : BaseProvider($"{hostname}/v1/"), IProvider +public sealed class ProviderSelfHosted(Settings.Provider provider) : BaseProvider($"{provider.Hostname}{provider.Host.BaseURL()}"), IProvider { private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new() { @@ -33,7 +33,7 @@ public sealed class ProviderSelfHosted(string hostname) : BaseProvider($"{hostna // 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 @@ -62,7 +62,7 @@ public sealed class ProviderSelfHosted(string hostname) : BaseProvider($"{hostna }, JSON_SERIALIZER_OPTIONS); // Build the HTTP post request: - var request = new HttpRequestMessage(HttpMethod.Post, "chat/completions"); + var request = new HttpRequestMessage(HttpMethod.Post, provider.Host.ChatURL()); // Set the content: request.Content = new StringContent(providerChatRequest, Encoding.UTF8, "application/json"); @@ -137,17 +137,33 @@ public sealed class ProviderSelfHosted(string hostname) : BaseProvider($"{hostna 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/Data.cs b/app/MindWork AI Studio/Settings/Data.cs index e4efb07..ea77487 100644 --- a/app/MindWork AI Studio/Settings/Data.cs +++ b/app/MindWork AI Studio/Settings/Data.cs @@ -9,7 +9,7 @@ public sealed class Data /// The version of the settings file. Allows us to upgrade the settings /// when a new version is available. /// - public Version Version { get; init; } = Version.V2; + public Version Version { get; init; } = Version.V3; /// /// List of configured providers. diff --git a/app/MindWork AI Studio/Settings/Provider.cs b/app/MindWork AI Studio/Settings/Provider.cs index 4813326..a59eb9b 100644 --- a/app/MindWork AI Studio/Settings/Provider.cs +++ b/app/MindWork AI Studio/Settings/Provider.cs @@ -1,5 +1,7 @@ using AIStudio.Provider; +using Host = AIStudio.Provider.SelfHosted.Host; + namespace AIStudio.Settings; /// @@ -12,7 +14,15 @@ namespace AIStudio.Settings; /// Whether the provider is self-hosted. /// The hostname of the provider. Useful for self-hosted providers. /// The LLM model to use for chat. -public readonly record struct Provider(uint Num, string Id, string InstanceName, Providers UsedProvider, Model Model, bool IsSelfHosted = false, string Hostname = "http://localhost:1234") +public readonly record struct Provider( + uint Num, + string Id, + string InstanceName, + Providers UsedProvider, + Model Model, + bool IsSelfHosted = false, + string Hostname = "http://localhost:1234", + Host Host = Host.NONE) { #region Overrides of ValueType @@ -24,7 +34,7 @@ public readonly record struct Provider(uint Num, string Id, string InstanceName, public override string ToString() { if(this.IsSelfHosted) - return $"{this.InstanceName} ({this.UsedProvider.ToName()}, {this.Hostname}, {this.Model})"; + return $"{this.InstanceName} ({this.UsedProvider.ToName()}, {this.Host}, {this.Hostname}, {this.Model})"; return $"{this.InstanceName} ({this.UsedProvider.ToName()}, {this.Model})"; } diff --git a/app/MindWork AI Studio/Settings/ProviderDialog.razor b/app/MindWork AI Studio/Settings/ProviderDialog.razor index 82bb5fc..e83740e 100644 --- a/app/MindWork AI Studio/Settings/ProviderDialog.razor +++ b/app/MindWork AI Studio/Settings/ProviderDialog.razor @@ -1,4 +1,5 @@ @using AIStudio.Provider +@using AIStudio.Provider.SelfHosted @using MudBlazor @@ -39,11 +40,19 @@ AdornmentIcon="@Icons.Material.Filled.Dns" AdornmentColor="Color.Info" Validation="@this.ValidatingHostname" + UserAttributes="@SPELLCHECK_ATTRIBUTES" /> + + @foreach (Host host in Enum.GetValues(typeof(Host))) + { + @host.Name() + } + + - Load - + Load + @foreach (var model in this.availableModels) { @model @@ -61,7 +70,7 @@ AdornmentIcon="@Icons.Material.Filled.Lightbulb" AdornmentColor="Color.Info" Validation="@this.ValidatingInstanceName" - UserAttributes="@INSTANCE_NAME_ATTRIBUTES" + UserAttributes="@SPELLCHECK_ATTRIBUTES" /> diff --git a/app/MindWork AI Studio/Settings/ProviderDialog.razor.cs b/app/MindWork AI Studio/Settings/ProviderDialog.razor.cs index 3febabc..edd00e9 100644 --- a/app/MindWork AI Studio/Settings/ProviderDialog.razor.cs +++ b/app/MindWork AI Studio/Settings/ProviderDialog.razor.cs @@ -4,6 +4,8 @@ using AIStudio.Provider; using Microsoft.AspNetCore.Components; +using Host = AIStudio.Provider.SelfHosted.Host; + namespace AIStudio.Settings; /// @@ -38,6 +40,12 @@ public partial class ProviderDialog : ComponentBase [Parameter] public string DataHostname { get; set; } = string.Empty; + /// + /// The local host to use, e.g., llama.cpp. + /// + [Parameter] + public Host DataHost { get; set; } = Host.NONE; + /// /// Is this provider self-hosted? /// @@ -68,7 +76,7 @@ public partial class ProviderDialog : ComponentBase [Inject] private IJSRuntime JsRuntime { get; set; } = null!; - private static readonly Dictionary INSTANCE_NAME_ATTRIBUTES = new(); + private static readonly Dictionary SPELLCHECK_ATTRIBUTES = new(); /// /// The list of used instance names. We need this to check for uniqueness. @@ -85,13 +93,25 @@ public partial class ProviderDialog : ComponentBase private MudForm form = null!; private readonly List availableModels = new(); + + private Provider CreateProviderSettings() => new() + { + Num = this.DataNum, + Id = this.DataId, + InstanceName = this.DataInstanceName, + UsedProvider = this.DataProvider, + Model = this.DataModel, + IsSelfHosted = this.DataProvider is Providers.SELF_HOSTED, + Hostname = this.DataHostname, + Host = this.DataHost, + }; #region Overrides of ComponentBase protected override async Task OnInitializedAsync() { // Configure the spellchecking for the instance name input: - this.SettingsManager.InjectSpellchecking(INSTANCE_NAME_ATTRIBUTES); + this.SettingsManager.InjectSpellchecking(SPELLCHECK_ATTRIBUTES); // Load the used instance names: this.UsedInstanceNames = this.SettingsManager.ConfigurationData.Providers.Select(x => x.InstanceName.ToLowerInvariant()).ToList(); @@ -100,9 +120,24 @@ public partial class ProviderDialog : ComponentBase if(this.IsEditing) { this.dataEditingPreviousInstanceName = this.DataInstanceName.ToLowerInvariant(); - var provider = this.DataProvider.CreateProvider(this.DataInstanceName); - if(provider is NoProvider) + + // + // We cannot load the API key for self-hosted providers: + // + if (this.DataProvider is Providers.SELF_HOSTED) + { + await this.ReloadModels(); + await base.OnInitializedAsync(); return; + } + + var loadedProviderSettings = this.CreateProviderSettings(); + var provider = loadedProviderSettings.CreateProvider(); + if(provider is NoProvider) + { + await base.OnInitializedAsync(); + return; + } // Load the API key: var requestedSecret = await this.SettingsManager.GetAPIKey(this.JsRuntime, provider); @@ -111,8 +146,7 @@ public partial class ProviderDialog : ComponentBase this.dataAPIKey = requestedSecret.Secret; // Now, we try to load the list of available models: - if(this.DataProvider is not Providers.SELF_HOSTED) - await this.ReloadModels(); + await this.ReloadModels(); } else { @@ -148,30 +182,23 @@ public partial class ProviderDialog : ComponentBase // Use the data model to store the provider. // We just return this data to the parent component: - var addedProvider = new Provider + var addedProviderSettings = this.CreateProviderSettings(); + if (addedProviderSettings.UsedProvider != Providers.SELF_HOSTED) { - Num = this.DataNum, - Id = this.DataId, - InstanceName = this.DataInstanceName, - UsedProvider = this.DataProvider, - Model = this.DataModel, - IsSelfHosted = this.DataProvider is Providers.SELF_HOSTED, - Hostname = this.DataHostname, - }; - - // We need to instantiate the provider to store the API key: - var provider = this.DataProvider.CreateProvider(this.DataInstanceName); + // 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; + // 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(addedProvider)); + + this.MudDialog.Close(DialogResult.Ok(addedProviderSettings)); } private string? ValidatingProvider(Providers provider) @@ -182,9 +209,20 @@ public partial class ProviderDialog : ComponentBase return null; } + private string? ValidatingHost(Host host) + { + if(this.DataProvider is not Providers.SELF_HOSTED) + return null; + + if (host == Host.NONE) + return "Please select a host."; + + return null; + } + 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) @@ -196,7 +234,7 @@ public partial class ProviderDialog : ComponentBase [GeneratedRegex(@"^[a-zA-Z0-9\-_. ]+$")] private static partial Regex InstanceNameRegex(); - private static readonly string[] RESERVED_NAMES = { "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9" }; + private static readonly string[] RESERVED_NAMES = ["CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"]; private string? ValidatingInstanceName(string instanceName) { @@ -270,7 +308,8 @@ public partial class ProviderDialog : ComponentBase private async Task ReloadModels() { - var provider = this.DataProvider.CreateProvider("temp"); + var currentProviderSettings = this.CreateProviderSettings(); + var provider = currentProviderSettings.CreateProvider(); if(provider is NoProvider) return; @@ -283,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 { diff --git a/app/MindWork AI Studio/Settings/SettingsMigrations.cs b/app/MindWork AI Studio/Settings/SettingsMigrations.cs index e578378..516a1c1 100644 --- a/app/MindWork AI Studio/Settings/SettingsMigrations.cs +++ b/app/MindWork AI Studio/Settings/SettingsMigrations.cs @@ -1,3 +1,5 @@ +using Host = AIStudio.Provider.SelfHosted.Host; + namespace AIStudio.Settings; public static class SettingsMigrations @@ -7,7 +9,11 @@ public static class SettingsMigrations switch (previousData.Version) { case Version.V1: - return MigrateFromV1(previousData); + previousData = MigrateV1ToV2(previousData); + return MigrateV2ToV3(previousData); + + case Version.V2: + return MigrateV2ToV3(previousData); default: Console.WriteLine("No migration needed."); @@ -15,7 +21,7 @@ public static class SettingsMigrations } } - private static Data MigrateFromV1(Data previousData) + private static Data MigrateV1ToV2(Data previousData) { // // Summary: @@ -36,4 +42,33 @@ public static class SettingsMigrations UpdateBehavior = previousData.UpdateBehavior, }; } + + private static Data MigrateV2ToV3(Data previousData) + { + // + // Summary: + // In v2, self-hosted providers had no host (LM Studio, llama.cpp, ollama, etc.) + // + + Console.WriteLine("Migrating from v2 to v3..."); + return new() + { + Version = Version.V3, + Providers = previousData.Providers.Select(provider => + { + if(provider.IsSelfHosted) + return provider with { Host = Host.LM_STUDIO }; + + return provider with { Host = Host.NONE }; + }).ToList(), + + EnableSpellchecking = previousData.EnableSpellchecking, + IsSavingEnergy = previousData.IsSavingEnergy, + NextProviderNum = previousData.NextProviderNum, + ShortcutSendBehavior = previousData.ShortcutSendBehavior, + UpdateBehavior = previousData.UpdateBehavior, + WorkspaceStorageBehavior = previousData.WorkspaceStorageBehavior, + WorkspaceStorageTemporaryMaintenancePolicy = previousData.WorkspaceStorageTemporaryMaintenancePolicy, + }; + } } \ No newline at end of file diff --git a/app/MindWork AI Studio/Settings/Version.cs b/app/MindWork AI Studio/Settings/Version.cs index 04e54ef..cb30052 100644 --- a/app/MindWork AI Studio/Settings/Version.cs +++ b/app/MindWork AI Studio/Settings/Version.cs @@ -10,4 +10,5 @@ public enum Version V1, V2, + V3, } \ No newline at end of file diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.8.1.md b/app/MindWork AI Studio/wwwroot/changelog/v0.8.1.md index 83d09dd..9a7bded 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v0.8.1.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.8.1.md @@ -1,2 +1,4 @@ -# v0.8.1, build 163 (2024-07-15 14:56 UTC) +# v0.8.1, build 163 (2024-07-16 08:28 UTC) +- Added support for ollama as a self-hosted provider +- Added support for model selection of self-hosted providers - Fixed a bug where the spellchecking setting was not applied to assistants \ No newline at end of file