mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2025-02-05 17:49:05 +00:00
Add support for ollama (#34)
This commit is contained in:
parent
179f350af5
commit
063fd0fcfa
@ -1,4 +1,6 @@
|
|||||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AI/@EntryIndexedValue">AI</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AI/@EntryIndexedValue">AI</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LM/@EntryIndexedValue">LM</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MSG/@EntryIndexedValue">MSG</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MSG/@EntryIndexedValue">MSG</s:String>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=ollama/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=tauri_0027s/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=tauri_0027s/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
@ -27,7 +27,7 @@ public abstract partial class AssistantBase : ComponentBase
|
|||||||
|
|
||||||
protected static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new();
|
protected static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new();
|
||||||
|
|
||||||
protected AIStudio.Settings.Provider selectedProvider;
|
protected AIStudio.Settings.Provider providerSettings;
|
||||||
protected MudForm? form;
|
protected MudForm? form;
|
||||||
protected bool inputIsValid;
|
protected bool inputIsValid;
|
||||||
|
|
||||||
@ -109,6 +109,6 @@ public abstract partial class AssistantBase : ComponentBase
|
|||||||
// Use the selected provider to get the AI response.
|
// Use the selected provider to get the AI response.
|
||||||
// By awaiting this line, we wait for the entire
|
// By awaiting this line, we wait for the entire
|
||||||
// content to be streamed.
|
// content to be streamed.
|
||||||
await aiText.CreateFromProviderAsync(this.selectedProvider.UsedProvider.CreateProvider(this.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);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -15,7 +15,7 @@
|
|||||||
}
|
}
|
||||||
</MudText>
|
</MudText>
|
||||||
|
|
||||||
<MudSelect T="Provider" @bind-Value="@this.selectedProvider" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Apps" Margin="Margin.Dense" Label="Provider" Class="mb-2 rounded-lg" Variant="Variant.Outlined">
|
<MudSelect T="Provider" @bind-Value="@this.providerSettings" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Apps" Margin="Margin.Dense" Label="Provider" Class="mb-2 rounded-lg" Variant="Variant.Outlined">
|
||||||
@foreach (var provider in this.SettingsManager.ConfigurationData.Providers)
|
@foreach (var provider in this.SettingsManager.ConfigurationData.Providers)
|
||||||
{
|
{
|
||||||
<MudSelectItem Value="@provider"/>
|
<MudSelectItem Value="@provider"/>
|
||||||
|
@ -32,7 +32,7 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable
|
|||||||
private const Placement TOOLBAR_TOOLTIP_PLACEMENT = Placement.Bottom;
|
private const Placement TOOLBAR_TOOLTIP_PLACEMENT = Placement.Bottom;
|
||||||
private static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new();
|
private static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new();
|
||||||
|
|
||||||
private AIStudio.Settings.Provider selectedProvider;
|
private AIStudio.Settings.Provider providerSettings;
|
||||||
private ChatThread? chatThread;
|
private ChatThread? chatThread;
|
||||||
private bool hasUnsavedChanges;
|
private bool hasUnsavedChanges;
|
||||||
private bool isStreaming;
|
private bool isStreaming;
|
||||||
@ -61,11 +61,11 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable
|
|||||||
|
|
||||||
#endregion
|
#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 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;
|
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.
|
// Use the selected provider to get the AI response.
|
||||||
// By awaiting this line, we wait for the entire
|
// By awaiting this line, we wait for the entire
|
||||||
// content to be streamed.
|
// content to be streamed.
|
||||||
await aiText.CreateFromProviderAsync(this.selectedProvider.UsedProvider.CreateProvider(this.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:
|
// Save the chat:
|
||||||
if (this.SettingsManager.ConfigurationData.WorkspaceStorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY)
|
if (this.SettingsManager.ConfigurationData.WorkspaceStorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY)
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
}
|
}
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
|
||||||
<MudSelect T="Provider" @bind-Value="@this.selectedProvider" Validation="@this.ValidatingProvider" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Apps" Margin="Margin.Dense" Label="Provider" Class="mb-3 rounded-lg" Variant="Variant.Outlined">
|
<MudSelect T="Provider" @bind-Value="@this.providerSettings" Validation="@this.ValidatingProvider" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Apps" Margin="Margin.Dense" Label="Provider" Class="mb-3 rounded-lg" Variant="Variant.Outlined">
|
||||||
@foreach (var provider in this.SettingsManager.ConfigurationData.Providers)
|
@foreach (var provider in this.SettingsManager.ConfigurationData.Providers)
|
||||||
{
|
{
|
||||||
<MudSelectItem Value="@provider"/>
|
<MudSelectItem Value="@provider"/>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
@page "/settings"
|
@page "/settings"
|
||||||
@using AIStudio.Provider
|
@using AIStudio.Provider
|
||||||
|
@using Host = AIStudio.Provider.SelfHosted.Host
|
||||||
|
|
||||||
<MudText Typo="Typo.h3" Class="mb-12">Settings</MudText>
|
<MudText Typo="Typo.h3" Class="mb-12">Settings</MudText>
|
||||||
|
|
||||||
@ -26,10 +27,18 @@
|
|||||||
<MudTd>@context.InstanceName</MudTd>
|
<MudTd>@context.InstanceName</MudTd>
|
||||||
<MudTd>@context.UsedProvider</MudTd>
|
<MudTd>@context.UsedProvider</MudTd>
|
||||||
<MudTd>
|
<MudTd>
|
||||||
@if(context.UsedProvider is not Providers.SELF_HOSTED)
|
@if (context.UsedProvider is not Providers.SELF_HOSTED)
|
||||||
|
{
|
||||||
@context.Model
|
@context.Model
|
||||||
|
}
|
||||||
|
else if (context.UsedProvider is Providers.SELF_HOSTED && context.Host is not Host.LLAMACPP)
|
||||||
|
{
|
||||||
|
@context.Model
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
@("as selected by provider")
|
@("as selected by provider")
|
||||||
|
}
|
||||||
</MudTd>
|
</MudTd>
|
||||||
<MudTd Style="text-align: left;">
|
<MudTd Style="text-align: left;">
|
||||||
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.OpenInBrowser" Class="ma-2" Href="@this.GetProviderDashboardURL(context.UsedProvider)" Target="_blank" Disabled="@(context.UsedProvider is Providers.NONE or Providers.SELF_HOSTED)">
|
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.OpenInBrowser" Class="ma-2" Href="@this.GetProviderDashboardURL(context.UsedProvider)" Target="_blank" Disabled="@(context.UsedProvider is Providers.NONE or Providers.SELF_HOSTED)">
|
||||||
|
@ -53,6 +53,7 @@ public partial class Settings : ComponentBase
|
|||||||
{ x => x.DataHostname, provider.Hostname },
|
{ x => x.DataHostname, provider.Hostname },
|
||||||
{ x => x.IsSelfHosted, provider.IsSelfHosted },
|
{ x => x.IsSelfHosted, provider.IsSelfHosted },
|
||||||
{ x => x.IsEditing, true },
|
{ x => x.IsEditing, true },
|
||||||
|
{ 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 Provider", dialogParameters, DialogOptions.FULLSCREEN);
|
||||||
@ -83,7 +84,7 @@ public partial class Settings : ComponentBase
|
|||||||
if (dialogResult.Canceled)
|
if (dialogResult.Canceled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var providerInstance = provider.UsedProvider.CreateProvider(provider.InstanceName, provider.Hostname);
|
var providerInstance = provider.CreateProvider();
|
||||||
var deleteSecretResponse = await this.SettingsManager.DeleteAPIKey(this.JsRuntime, providerInstance);
|
var deleteSecretResponse = await this.SettingsManager.DeleteAPIKey(this.JsRuntime, providerInstance);
|
||||||
if(deleteSecretResponse.Success)
|
if(deleteSecretResponse.Success)
|
||||||
{
|
{
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
}
|
}
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
|
||||||
<MudSelect T="Provider" @bind-Value="@this.selectedProvider" Validation="@this.ValidatingProvider" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Apps" Margin="Margin.Dense" Label="Provider" Class="mb-3 rounded-lg" Variant="Variant.Outlined">
|
<MudSelect T="Provider" @bind-Value="@this.providerSettings" Validation="@this.ValidatingProvider" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Apps" Margin="Margin.Dense" Label="Provider" Class="mb-3 rounded-lg" Variant="Variant.Outlined">
|
||||||
@foreach (var provider in this.SettingsManager.ConfigurationData.Providers)
|
@foreach (var provider in this.SettingsManager.ConfigurationData.Providers)
|
||||||
{
|
{
|
||||||
<MudSelectItem Value="@provider"/>
|
<MudSelectItem Value="@provider"/>
|
||||||
|
@ -38,7 +38,7 @@ else
|
|||||||
}
|
}
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
|
||||||
<MudSelect T="Provider" @bind-Value="@this.selectedProvider" Validation="@this.ValidatingProvider" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Apps" Margin="Margin.Dense" Label="Provider" Class="mb-3 rounded-lg" Variant="Variant.Outlined">
|
<MudSelect T="Provider" @bind-Value="@this.providerSettings" Validation="@this.ValidatingProvider" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Apps" Margin="Margin.Dense" Label="Provider" Class="mb-3 rounded-lg" Variant="Variant.Outlined">
|
||||||
@foreach (var provider in this.SettingsManager.ConfigurationData.Providers)
|
@foreach (var provider in this.SettingsManager.ConfigurationData.Providers)
|
||||||
{
|
{
|
||||||
<MudSelectItem Value="@provider"/>
|
<MudSelectItem Value="@provider"/>
|
||||||
|
@ -45,17 +45,15 @@ public static class ExtensionsProvider
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new provider instance based on the provider value.
|
/// Creates a new provider instance based on the provider value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="provider">The provider value.</param>
|
/// <param name="providerSettings">The provider settings.</param>
|
||||||
/// <param name="instanceName">The used instance name.</param>
|
|
||||||
/// <param name="hostname">The hostname of the provider.</param>
|
|
||||||
/// <returns>The provider instance.</returns>
|
/// <returns>The provider instance.</returns>
|
||||||
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.OPEN_AI => new ProviderOpenAI { InstanceName = providerSettings.InstanceName },
|
||||||
Providers.ANTHROPIC => new ProviderAnthropic { InstanceName = instanceName },
|
Providers.ANTHROPIC => new ProviderAnthropic { InstanceName = providerSettings.InstanceName },
|
||||||
Providers.MISTRAL => new ProviderMistral { InstanceName = 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(),
|
_ => new NoProvider(),
|
||||||
};
|
};
|
||||||
|
42
app/MindWork AI Studio/Provider/SelfHosted/Host.cs
Normal file
42
app/MindWork AI Studio/Provider/SelfHosted/Host.cs
Normal file
@ -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",
|
||||||
|
};
|
||||||
|
}
|
@ -8,7 +8,7 @@ using AIStudio.Settings;
|
|||||||
|
|
||||||
namespace AIStudio.Provider.SelfHosted;
|
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()
|
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:
|
// Prepare the OpenAI HTTP chat request:
|
||||||
var providerChatRequest = JsonSerializer.Serialize(new ChatRequest
|
var providerChatRequest = JsonSerializer.Serialize(new ChatRequest
|
||||||
{
|
{
|
||||||
Model = (await this.GetTextModels(jsRuntime, settings, token: token)).First().Id,
|
Model = chatModel.Id,
|
||||||
|
|
||||||
// Build the messages:
|
// Build the messages:
|
||||||
// - First of all the system prompt
|
// - First of all the system prompt
|
||||||
@ -62,7 +62,7 @@ public sealed class ProviderSelfHosted(string hostname) : BaseProvider($"{hostna
|
|||||||
}, JSON_SERIALIZER_OPTIONS);
|
}, JSON_SERIALIZER_OPTIONS);
|
||||||
|
|
||||||
// Build the HTTP post request:
|
// 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:
|
// Set the content:
|
||||||
request.Content = new StringContent(providerChatRequest, Encoding.UTF8, "application/json");
|
request.Content = new StringContent(providerChatRequest, Encoding.UTF8, "application/json");
|
||||||
@ -137,17 +137,33 @@ public sealed class ProviderSelfHosted(string hostname) : BaseProvider($"{hostna
|
|||||||
|
|
||||||
public async Task<IEnumerable<Provider.Model>> GetTextModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default)
|
public async Task<IEnumerable<Provider.Model>> GetTextModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
var request = new HttpRequestMessage(HttpMethod.Get, "models");
|
try
|
||||||
var response = await this.httpClient.SendAsync(request, token);
|
{
|
||||||
if(!response.IsSuccessStatusCode)
|
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 [];
|
return [];
|
||||||
|
|
||||||
var modelResponse = await response.Content.ReadFromJsonAsync<ModelsResponse>(token);
|
var lmStudioModelResponse = await lmStudioResponse.Content.ReadFromJsonAsync<ModelsResponse>(token);
|
||||||
if (modelResponse.Data.Length > 1)
|
return lmStudioModelResponse.Data.Select(n => new Provider.Model(n.Id));
|
||||||
Console.WriteLine("Warning: multiple models found; using the first one.");
|
}
|
||||||
|
|
||||||
var firstModel = modelResponse.Data.First();
|
return [];
|
||||||
return [ new Provider.Model(firstModel.Id) ];
|
}
|
||||||
|
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
|
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||||
|
@ -9,7 +9,7 @@ public sealed class Data
|
|||||||
/// The version of the settings file. Allows us to upgrade the settings
|
/// The version of the settings file. Allows us to upgrade the settings
|
||||||
/// when a new version is available.
|
/// when a new version is available.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Version Version { get; init; } = Version.V2;
|
public Version Version { get; init; } = Version.V3;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// List of configured providers.
|
/// List of configured providers.
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
using AIStudio.Provider;
|
using AIStudio.Provider;
|
||||||
|
|
||||||
|
using Host = AIStudio.Provider.SelfHosted.Host;
|
||||||
|
|
||||||
namespace AIStudio.Settings;
|
namespace AIStudio.Settings;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -12,7 +14,15 @@ namespace AIStudio.Settings;
|
|||||||
/// <param name="IsSelfHosted">Whether the provider is self-hosted.</param>
|
/// <param name="IsSelfHosted">Whether the provider is self-hosted.</param>
|
||||||
/// <param name="Hostname">The hostname of the provider. Useful for self-hosted providers.</param>
|
/// <param name="Hostname">The hostname of the provider. Useful for self-hosted providers.</param>
|
||||||
/// <param name="Model">The LLM model to use for chat.</param>
|
/// <param name="Model">The LLM model to use for chat.</param>
|
||||||
public readonly record struct Provider(uint Num, string Id, string InstanceName, Providers UsedProvider, Model Model, 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
|
#region Overrides of ValueType
|
||||||
|
|
||||||
@ -24,7 +34,7 @@ public readonly record struct Provider(uint Num, string Id, string InstanceName,
|
|||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
if(this.IsSelfHosted)
|
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})";
|
return $"{this.InstanceName} ({this.UsedProvider.ToName()}, {this.Model})";
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
@using AIStudio.Provider
|
@using AIStudio.Provider
|
||||||
|
@using AIStudio.Provider.SelfHosted
|
||||||
@using MudBlazor
|
@using MudBlazor
|
||||||
|
|
||||||
<MudDialog>
|
<MudDialog>
|
||||||
@ -39,11 +40,19 @@
|
|||||||
AdornmentIcon="@Icons.Material.Filled.Dns"
|
AdornmentIcon="@Icons.Material.Filled.Dns"
|
||||||
AdornmentColor="Color.Info"
|
AdornmentColor="Color.Info"
|
||||||
Validation="@this.ValidatingHostname"
|
Validation="@this.ValidatingHostname"
|
||||||
|
UserAttributes="@SPELLCHECK_ATTRIBUTES"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<MudSelect Disabled="@this.IsCloudProvider" @bind-Value="@this.DataHost" Label="Host" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.ValidatingHost">
|
||||||
|
@foreach (Host host in Enum.GetValues(typeof(Host)))
|
||||||
|
{
|
||||||
|
<MudSelectItem Value="@host">@host.Name()</MudSelectItem>
|
||||||
|
}
|
||||||
|
</MudSelect>
|
||||||
|
|
||||||
<MudStack Row="@true" AlignItems="AlignItems.Center">
|
<MudStack Row="@true" AlignItems="AlignItems.Center">
|
||||||
<MudButton Disabled="@(!this.CanLoadModels)" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="this.ReloadModels">Load</MudButton>
|
<MudButton Disabled="@(!this.CanLoadModels())" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="this.ReloadModels">Load</MudButton>
|
||||||
<MudSelect Disabled="@this.IsSelfHostedOrNone" @bind-Value="@this.DataModel" Label="Model" Class="mb-3" OpenIcon="@Icons.Material.Filled.FaceRetouchingNatural" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.ValidatingModel">
|
<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">
|
||||||
@foreach (var model in this.availableModels)
|
@foreach (var model in this.availableModels)
|
||||||
{
|
{
|
||||||
<MudSelectItem Value="@model">@model</MudSelectItem>
|
<MudSelectItem Value="@model">@model</MudSelectItem>
|
||||||
@ -61,7 +70,7 @@
|
|||||||
AdornmentIcon="@Icons.Material.Filled.Lightbulb"
|
AdornmentIcon="@Icons.Material.Filled.Lightbulb"
|
||||||
AdornmentColor="Color.Info"
|
AdornmentColor="Color.Info"
|
||||||
Validation="@this.ValidatingInstanceName"
|
Validation="@this.ValidatingInstanceName"
|
||||||
UserAttributes="@INSTANCE_NAME_ATTRIBUTES"
|
UserAttributes="@SPELLCHECK_ATTRIBUTES"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</MudForm>
|
</MudForm>
|
||||||
|
@ -4,6 +4,8 @@ using AIStudio.Provider;
|
|||||||
|
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
using Host = AIStudio.Provider.SelfHosted.Host;
|
||||||
|
|
||||||
namespace AIStudio.Settings;
|
namespace AIStudio.Settings;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -38,6 +40,12 @@ public partial class ProviderDialog : ComponentBase
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public string DataHostname { get; set; } = string.Empty;
|
public string DataHostname { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The local host to use, e.g., llama.cpp.
|
||||||
|
/// </summary>
|
||||||
|
[Parameter]
|
||||||
|
public Host DataHost { get; set; } = Host.NONE;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Is this provider self-hosted?
|
/// Is this provider self-hosted?
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -68,7 +76,7 @@ public partial class ProviderDialog : ComponentBase
|
|||||||
[Inject]
|
[Inject]
|
||||||
private IJSRuntime JsRuntime { get; set; } = null!;
|
private IJSRuntime JsRuntime { get; set; } = null!;
|
||||||
|
|
||||||
private static readonly Dictionary<string, object?> INSTANCE_NAME_ATTRIBUTES = new();
|
private static readonly Dictionary<string, object?> SPELLCHECK_ATTRIBUTES = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The list of used instance names. We need this to check for uniqueness.
|
/// The list of used instance names. We need this to check for uniqueness.
|
||||||
@ -86,12 +94,24 @@ public partial class ProviderDialog : ComponentBase
|
|||||||
|
|
||||||
private readonly List<Model> availableModels = new();
|
private readonly List<Model> 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
|
#region Overrides of ComponentBase
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
// Configure the spellchecking for the instance name input:
|
// Configure the spellchecking for the instance name input:
|
||||||
this.SettingsManager.InjectSpellchecking(INSTANCE_NAME_ATTRIBUTES);
|
this.SettingsManager.InjectSpellchecking(SPELLCHECK_ATTRIBUTES);
|
||||||
|
|
||||||
// Load the used instance names:
|
// Load the used instance names:
|
||||||
this.UsedInstanceNames = this.SettingsManager.ConfigurationData.Providers.Select(x => x.InstanceName.ToLowerInvariant()).ToList();
|
this.UsedInstanceNames = this.SettingsManager.ConfigurationData.Providers.Select(x => x.InstanceName.ToLowerInvariant()).ToList();
|
||||||
@ -100,9 +120,24 @@ public partial class ProviderDialog : ComponentBase
|
|||||||
if(this.IsEditing)
|
if(this.IsEditing)
|
||||||
{
|
{
|
||||||
this.dataEditingPreviousInstanceName = this.DataInstanceName.ToLowerInvariant();
|
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;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var loadedProviderSettings = this.CreateProviderSettings();
|
||||||
|
var provider = loadedProviderSettings.CreateProvider();
|
||||||
|
if(provider is NoProvider)
|
||||||
|
{
|
||||||
|
await base.OnInitializedAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Load the API key:
|
// Load the API key:
|
||||||
var requestedSecret = await this.SettingsManager.GetAPIKey(this.JsRuntime, provider);
|
var requestedSecret = await this.SettingsManager.GetAPIKey(this.JsRuntime, provider);
|
||||||
@ -111,7 +146,6 @@ public partial class ProviderDialog : ComponentBase
|
|||||||
this.dataAPIKey = requestedSecret.Secret;
|
this.dataAPIKey = requestedSecret.Secret;
|
||||||
|
|
||||||
// Now, we try to load the list of available models:
|
// 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
|
else
|
||||||
@ -148,19 +182,11 @@ public partial class ProviderDialog : ComponentBase
|
|||||||
|
|
||||||
// Use the data model to store the provider.
|
// Use the data model to store the provider.
|
||||||
// We just return this data to the parent component:
|
// 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:
|
// We need to instantiate the provider to store the API key:
|
||||||
var provider = this.DataProvider.CreateProvider(this.DataInstanceName);
|
var provider = addedProviderSettings.CreateProvider();
|
||||||
|
|
||||||
// Store the API key in the OS secure storage:
|
// Store the API key in the OS secure storage:
|
||||||
var storeResponse = await this.SettingsManager.SetAPIKey(this.JsRuntime, provider, this.dataAPIKey);
|
var storeResponse = await this.SettingsManager.SetAPIKey(this.JsRuntime, provider, this.dataAPIKey);
|
||||||
@ -170,8 +196,9 @@ public partial class ProviderDialog : ComponentBase
|
|||||||
await this.form.Validate();
|
await this.form.Validate();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.MudDialog.Close(DialogResult.Ok(addedProvider));
|
this.MudDialog.Close(DialogResult.Ok(addedProviderSettings));
|
||||||
}
|
}
|
||||||
|
|
||||||
private string? ValidatingProvider(Providers provider)
|
private string? ValidatingProvider(Providers provider)
|
||||||
@ -182,9 +209,20 @@ public partial class ProviderDialog : ComponentBase
|
|||||||
return null;
|
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)
|
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;
|
return null;
|
||||||
|
|
||||||
if (model == default)
|
if (model == default)
|
||||||
@ -196,7 +234,7 @@ public partial class ProviderDialog : ComponentBase
|
|||||||
[GeneratedRegex(@"^[a-zA-Z0-9\-_. ]+$")]
|
[GeneratedRegex(@"^[a-zA-Z0-9\-_. ]+$")]
|
||||||
private static partial Regex InstanceNameRegex();
|
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)
|
private string? ValidatingInstanceName(string instanceName)
|
||||||
{
|
{
|
||||||
@ -270,7 +308,8 @@ public partial class ProviderDialog : ComponentBase
|
|||||||
|
|
||||||
private async Task ReloadModels()
|
private async Task ReloadModels()
|
||||||
{
|
{
|
||||||
var provider = this.DataProvider.CreateProvider("temp");
|
var currentProviderSettings = this.CreateProviderSettings();
|
||||||
|
var provider = currentProviderSettings.CreateProvider();
|
||||||
if(provider is NoProvider)
|
if(provider is NoProvider)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -283,12 +322,44 @@ public partial class ProviderDialog : ComponentBase
|
|||||||
this.availableModels.AddRange(orderedModels);
|
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 IsCloudProvider => this.DataProvider is not Providers.SELF_HOSTED;
|
||||||
|
|
||||||
private bool IsSelfHostedOrNone => this.DataProvider is Providers.SELF_HOSTED or Providers.NONE;
|
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
|
private string GetProviderCreationURL() => this.DataProvider switch
|
||||||
{
|
{
|
||||||
Providers.OPEN_AI => "https://platform.openai.com/signup",
|
Providers.OPEN_AI => "https://platform.openai.com/signup",
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
using Host = AIStudio.Provider.SelfHosted.Host;
|
||||||
|
|
||||||
namespace AIStudio.Settings;
|
namespace AIStudio.Settings;
|
||||||
|
|
||||||
public static class SettingsMigrations
|
public static class SettingsMigrations
|
||||||
@ -7,7 +9,11 @@ public static class SettingsMigrations
|
|||||||
switch (previousData.Version)
|
switch (previousData.Version)
|
||||||
{
|
{
|
||||||
case Version.V1:
|
case Version.V1:
|
||||||
return MigrateFromV1(previousData);
|
previousData = MigrateV1ToV2(previousData);
|
||||||
|
return MigrateV2ToV3(previousData);
|
||||||
|
|
||||||
|
case Version.V2:
|
||||||
|
return MigrateV2ToV3(previousData);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Console.WriteLine("No migration needed.");
|
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:
|
// Summary:
|
||||||
@ -36,4 +42,33 @@ public static class SettingsMigrations
|
|||||||
UpdateBehavior = previousData.UpdateBehavior,
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
@ -10,4 +10,5 @@ public enum Version
|
|||||||
|
|
||||||
V1,
|
V1,
|
||||||
V2,
|
V2,
|
||||||
|
V3,
|
||||||
}
|
}
|
@ -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
|
- Fixed a bug where the spellchecking setting was not applied to assistants
|
Loading…
Reference in New Issue
Block a user