mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-19 22:58:00 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a956b55d6b | ||
|
|
a9ca51377e | ||
|
|
880f86316b | ||
|
|
063fd0fcfa | ||
|
|
179f350af5 |
No files matched your search
@@ -5,7 +5,7 @@ MindWork AI Studio is a desktop application available for macOS, Windows, and Li
|
||||
|
||||
**Key advantages:**
|
||||
- **Free of charge**: The app is free to use, both for personal and commercial purposes.
|
||||
- **Independence**: Users are not tied to any single provider. Instead, they can choose the provider that best suits their needs. Right now, we support OpenAI (GPT4o etc.), Mistral, Anthropic (Claude), and self-hosted models using [llama.cpp](https://github.com/ggerganov/llama.cpp) or [LM Studio](https://lmstudio.ai/). Support for Google Gemini, [ollama](https://github.com/ollama/ollama), [Replicate](https://replicate.com/), and [Fireworks](https://fireworks.ai/) is planned.
|
||||
- **Independence**: Users are not tied to any single provider. Instead, they can choose the provider that best suits their needs. Right now, we support OpenAI (GPT4o etc.), Mistral, Anthropic (Claude), and self-hosted models using [llama.cpp](https://github.com/ggerganov/llama.cpp), [ollama](https://github.com/ollama/ollama), or [LM Studio](https://lmstudio.ai/). Support for Google Gemini, [Replicate](https://replicate.com/), and [Fireworks](https://fireworks.ai/) is planned.
|
||||
- **Unrestricted usage**: Unlike services like ChatGPT, which impose limits after intensive use, MindWork AI Studio offers unlimited usage through the providers API.
|
||||
- **Cost-effective**: You only pay for what you use, which can be cheaper than monthly subscription services like ChatGPT Plus, especially if used infrequently. But beware, here be dragons: For extremely intensive usage, the API costs can be significantly higher. Unfortunately, providers currently do not offer a way to display current costs in the app. Therefore, check your account with the respective provider to see how your costs are developing. When available, use prepaid and set a cost limit.
|
||||
- **Privacy**: The data entered into the app is not used for training by the providers since we are using the provider's API.
|
||||
|
||||
@@ -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">
|
||||
<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: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>
|
||||
@@ -25,7 +25,9 @@ public abstract partial class AssistantBase : ComponentBase
|
||||
|
||||
private protected virtual RenderFragment? Body => null;
|
||||
|
||||
protected AIStudio.Settings.Provider selectedProvider;
|
||||
protected static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new();
|
||||
|
||||
protected AIStudio.Settings.Provider providerSettings;
|
||||
protected MudForm? form;
|
||||
protected bool inputIsValid;
|
||||
|
||||
@@ -35,6 +37,14 @@ public abstract partial class AssistantBase : ComponentBase
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
// Configure the spellchecking for the user input:
|
||||
this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES);
|
||||
|
||||
await base.OnParametersSetAsync();
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
// Reset the validation when not editing and on the first render.
|
||||
@@ -47,6 +57,14 @@ public abstract partial class AssistantBase : ComponentBase
|
||||
|
||||
#endregion
|
||||
|
||||
protected string? ValidatingProvider(AIStudio.Settings.Provider provider)
|
||||
{
|
||||
if(provider.UsedProvider == Providers.NONE)
|
||||
return "Please select a provider.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void CreateChatThread()
|
||||
{
|
||||
this.chatThread = new()
|
||||
@@ -99,6 +117,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);
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,8 @@ public partial class Changelog
|
||||
|
||||
public static readonly Log[] LOGS =
|
||||
[
|
||||
new (164, "v0.8.2, build 164 (2024-07-16 18:03 UTC)", "v0.8.2.md"),
|
||||
new (163, "v0.8.1, build 163 (2024-07-16 08:32 UTC)", "v0.8.1.md"),
|
||||
new (162, "v0.8.0, build 162 (2024-07-14 19:39 UTC)", "v0.8.0.md"),
|
||||
new (161, "v0.7.1, build 161 (2024-07-13 11:42 UTC)", "v0.7.1.md"),
|
||||
new (160, "v0.7.0, build 160 (2024-07-13 08:21 UTC)", "v0.7.0.md"),
|
||||
|
||||
@@ -8,4 +8,5 @@
|
||||
<AssistantBlock Name="Icon Finder" Description="Using a LLM to find an icon for a given context." Icon="@Icons.Material.Filled.FindInPage" Link="/assistant/icons"/>
|
||||
<AssistantBlock Name="Text Summarizer" Description="Using a LLM to summarize a given text." Icon="@Icons.Material.Filled.TextSnippet" Link="/assistant/summarizer"/>
|
||||
<AssistantBlock Name="Translator" Description="Translate text into another language." Icon="@Icons.Material.Filled.Translate" Link="/assistant/translator"/>
|
||||
<AssistantBlock Name="Coding" Description="Get coding and debugging support from a LLM." Icon="@Icons.Material.Filled.Code" Link="/assistant/coding"/>
|
||||
</MudGrid>
|
||||
@@ -15,7 +15,7 @@
|
||||
}
|
||||
</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)
|
||||
{
|
||||
<MudSelectItem Value="@provider"/>
|
||||
|
||||
@@ -32,7 +32,7 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable
|
||||
private const Placement TOOLBAR_TOOLTIP_PLACEMENT = Placement.Bottom;
|
||||
private static readonly Dictionary<string, object?> 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)
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
@page "/assistant/coding"
|
||||
@using AIStudio.Settings
|
||||
@inherits AssistantBaseCore
|
||||
|
||||
<MudExpansionPanels Class="mb-3">
|
||||
@for (var contextIndex = 0; contextIndex < this.codingContexts.Count; contextIndex++)
|
||||
{
|
||||
var codingContext = this.codingContexts[contextIndex];
|
||||
<ExpansionPanel HeaderText="@codingContext.Id" HeaderIcon="@Icons.Material.Filled.Code">
|
||||
<CodingContextItem @bind-CodingContext="@codingContext"/>
|
||||
</ExpansionPanel>
|
||||
}
|
||||
</MudExpansionPanels>
|
||||
<MudButton Variant="Variant.Filled" OnClick="() => this.AddCodingContext()" Class="mb-3">
|
||||
Add context
|
||||
</MudButton>
|
||||
|
||||
<MudStack Row="@false" Class="mb-3">
|
||||
<MudSwitch T="bool" @bind-Value="@this.provideCompilerMessages">
|
||||
@(this.provideCompilerMessages ? "Provide compiler messages" : "Provide no compiler messages")
|
||||
</MudSwitch>
|
||||
@if (this.provideCompilerMessages)
|
||||
{
|
||||
<MudTextField T="string" @bind-Text="@this.compilerMessages" Validation="@this.ValidatingCompilerMessages" AdornmentIcon="@Icons.Material.Filled.Error" Adornment="Adornment.Start" Label="Compiler messages" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
|
||||
}
|
||||
</MudStack>
|
||||
|
||||
<MudTextField T="string" @bind-Text="@this.questions" Validation="@this.ValidateQuestions" AdornmentIcon="@Icons.Material.Filled.QuestionMark" Adornment="Adornment.Start" Label="Your question(s)" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
|
||||
|
||||
<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)
|
||||
{
|
||||
<MudSelectItem Value="@provider"/>
|
||||
}
|
||||
</MudSelect>
|
||||
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Info" OnClick="() => this.GetSupport()" Class="mb-3">
|
||||
Get support
|
||||
</MudButton>
|
||||
@@ -0,0 +1,106 @@
|
||||
using System.Text;
|
||||
|
||||
namespace AIStudio.Components.Pages.Coding;
|
||||
|
||||
public partial class AssistantCoding : AssistantBaseCore
|
||||
{
|
||||
protected override string Title => "Coding Assistant";
|
||||
|
||||
protected override string Description =>
|
||||
"""
|
||||
This coding assistant supports you in writing code. Provide some coding context by copying and pasting
|
||||
your code into the input fields. You might assign an ID to your code snippet to easily reference it later.
|
||||
When you have compiler messages, you can paste them into the input fields to get help with debugging as well.
|
||||
""";
|
||||
|
||||
protected override string SystemPrompt =>
|
||||
"""
|
||||
You are a friendly, helpful senior software developer with extensive experience in various programming languages
|
||||
and concepts. You are familiar with principles like DRY, KISS, YAGNI, and SOLID and can apply and explain them.
|
||||
You know object-oriented programming, as well as functional programming and procedural programming. You are also
|
||||
familiar with design patterns and can explain them. You are an expert of debugging and can help with compiler
|
||||
messages. You can also help with code refactoring and optimization.
|
||||
|
||||
When the user asks in a different language than English, you answer in the same language!
|
||||
""";
|
||||
|
||||
private readonly List<CodingContext> codingContexts = new();
|
||||
private bool provideCompilerMessages;
|
||||
private string compilerMessages = string.Empty;
|
||||
private string questions = string.Empty;
|
||||
|
||||
private string? ValidatingCompilerMessages(string checkCompilerMessages)
|
||||
{
|
||||
if(!this.provideCompilerMessages)
|
||||
return null;
|
||||
|
||||
if(string.IsNullOrWhiteSpace(checkCompilerMessages))
|
||||
return "Please provide the compiler messages.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? ValidateQuestions(string checkQuestions)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(checkQuestions))
|
||||
return "Please provide your questions.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void AddCodingContext()
|
||||
{
|
||||
this.codingContexts.Add(new()
|
||||
{
|
||||
Id = $"Context {this.codingContexts.Count + 1}",
|
||||
});
|
||||
}
|
||||
|
||||
private async Task GetSupport()
|
||||
{
|
||||
await this.form!.Validate();
|
||||
if (!this.inputIsValid)
|
||||
return;
|
||||
|
||||
var sbContext = new StringBuilder();
|
||||
if (this.codingContexts.Count > 0)
|
||||
{
|
||||
sbContext.AppendLine("I have the following coding context:");
|
||||
sbContext.AppendLine();
|
||||
foreach (var codingContext in this.codingContexts)
|
||||
{
|
||||
sbContext.AppendLine($"ID: {codingContext.Id}");
|
||||
sbContext.AppendLine($"Language: {codingContext.Language.Name()}");
|
||||
sbContext.AppendLine($"Other Language: {codingContext.OtherLanguage}");
|
||||
sbContext.AppendLine($"Content:");
|
||||
sbContext.AppendLine("```");
|
||||
sbContext.AppendLine(codingContext.Code);
|
||||
sbContext.AppendLine("```");
|
||||
sbContext.AppendLine();
|
||||
}
|
||||
}
|
||||
|
||||
var sbCompilerMessages = new StringBuilder();
|
||||
if (this.provideCompilerMessages)
|
||||
{
|
||||
sbCompilerMessages.AppendLine("I have the following compiler messages:");
|
||||
sbCompilerMessages.AppendLine();
|
||||
sbCompilerMessages.AppendLine("```");
|
||||
sbCompilerMessages.AppendLine(this.compilerMessages);
|
||||
sbCompilerMessages.AppendLine("```");
|
||||
sbCompilerMessages.AppendLine();
|
||||
}
|
||||
|
||||
this.CreateChatThread();
|
||||
var time = this.AddUserRequest(
|
||||
$"""
|
||||
{sbContext}
|
||||
{sbCompilerMessages}
|
||||
|
||||
My questions are:
|
||||
{this.questions}
|
||||
""");
|
||||
|
||||
await this.AddAIResponseAsync(time);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace AIStudio.Components.Pages.Coding;
|
||||
|
||||
public sealed class CodingContext(string id, CommonCodingLanguages language, string otherLanguage, string code)
|
||||
{
|
||||
public CodingContext() : this(string.Empty, CommonCodingLanguages.NONE, string.Empty, string.Empty)
|
||||
{
|
||||
}
|
||||
|
||||
public string Id { get; set; } = id;
|
||||
|
||||
public CommonCodingLanguages Language { get; set; } = language;
|
||||
|
||||
public string OtherLanguage { get; set; } = otherLanguage;
|
||||
|
||||
public string Code { get; set; } = code;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<MudTextField T="string" @bind-Text="@this.CodingContext.Id" AdornmentIcon="@Icons.Material.Filled.Numbers" Adornment="Adornment.Start" Label="(Optional) Identifier" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3"/>
|
||||
<MudStack Row="@true" AlignItems="AlignItems.Center" Class="mb-3">
|
||||
<MudSelect T="CommonCodingLanguages" @bind-Value="@this.CodingContext.Language" AdornmentIcon="@Icons.Material.Filled.Code" Adornment="Adornment.Start" Label="Language" Variant="Variant.Outlined" Margin="Margin.Dense">
|
||||
@foreach (var language in Enum.GetValues<CommonCodingLanguages>())
|
||||
{
|
||||
<MudSelectItem Value="@language">@language.Name()</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
@if (this.CodingContext.Language is CommonCodingLanguages.OTHER)
|
||||
{
|
||||
<MudTextField T="string" @bind-Text="@this.CodingContext.OtherLanguage" Validation="@this.ValidatingOtherLanguage" Label="Other language" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
|
||||
}
|
||||
</MudStack>
|
||||
<MudTextField T="string" @bind-Text="@this.CodingContext.Code" Validation="@this.ValidatingCode" AdornmentIcon="@Icons.Material.Filled.DocumentScanner" Adornment="Adornment.Start" Label="Your code" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES" />
|
||||
@@ -0,0 +1,50 @@
|
||||
using AIStudio.Settings;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Components.Pages.Coding;
|
||||
|
||||
public partial class CodingContextItem : ComponentBase
|
||||
{
|
||||
[Parameter]
|
||||
public CodingContext CodingContext { get; set; } = new();
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<CodingContext> CodingContextChanged { get; set; }
|
||||
|
||||
[Inject]
|
||||
protected SettingsManager SettingsManager { get; set; } = null!;
|
||||
|
||||
private static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new();
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
// Configure the spellchecking for the user input:
|
||||
this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES);
|
||||
|
||||
await base.OnParametersSetAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private string? ValidatingCode(string code)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(code))
|
||||
return $"{this.CodingContext.Id}: Please provide your input.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? ValidatingOtherLanguage(string language)
|
||||
{
|
||||
if(this.CodingContext.Language != CommonCodingLanguages.OTHER)
|
||||
return null;
|
||||
|
||||
if(string.IsNullOrWhiteSpace(language))
|
||||
return "Please specify the language.";
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
namespace AIStudio.Components.Pages.Coding;
|
||||
|
||||
public static class CommonCodingLanguageExtensions
|
||||
{
|
||||
public static string Name(this CommonCodingLanguages language) => language switch
|
||||
{
|
||||
CommonCodingLanguages.NONE => "None",
|
||||
|
||||
CommonCodingLanguages.BASH => "Bash",
|
||||
CommonCodingLanguages.BLAZOR => ".NET Blazor",
|
||||
CommonCodingLanguages.C => "C",
|
||||
CommonCodingLanguages.CPP => "C++",
|
||||
CommonCodingLanguages.CSHARP => "C#",
|
||||
CommonCodingLanguages.CSS => "CSS",
|
||||
CommonCodingLanguages.FORTRAN => "Fortran",
|
||||
CommonCodingLanguages.GDSCRIPT => "GDScript",
|
||||
CommonCodingLanguages.GO => "Go",
|
||||
CommonCodingLanguages.HTML => "HTML",
|
||||
CommonCodingLanguages.JAVA => "Java",
|
||||
CommonCodingLanguages.JAVASCRIPT => "JavaScript",
|
||||
CommonCodingLanguages.JSON => "JSON",
|
||||
CommonCodingLanguages.JULIA => "Julia",
|
||||
CommonCodingLanguages.KOTLIN => "Kotlin",
|
||||
CommonCodingLanguages.LUA => "Lua",
|
||||
CommonCodingLanguages.MARKDOWN => "Markdown",
|
||||
CommonCodingLanguages.MATHEMATICA => "Mathematica",
|
||||
CommonCodingLanguages.MATLAB => "MATLAB",
|
||||
CommonCodingLanguages.PHP => "PHP",
|
||||
CommonCodingLanguages.POWERSHELL => "PowerShell",
|
||||
CommonCodingLanguages.PROLOG => "Prolog",
|
||||
CommonCodingLanguages.PYTHON => "Python",
|
||||
CommonCodingLanguages.R => "R",
|
||||
CommonCodingLanguages.RUBY => "Ruby",
|
||||
CommonCodingLanguages.RUST => "Rust",
|
||||
CommonCodingLanguages.SQL => "SQL",
|
||||
CommonCodingLanguages.SWIFT => "Swift",
|
||||
CommonCodingLanguages.TYPESCRIPT => "TypeScript",
|
||||
CommonCodingLanguages.XML => "XML",
|
||||
|
||||
CommonCodingLanguages.OTHER => "Other",
|
||||
_ => "Unknown"
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
namespace AIStudio.Components.Pages.Coding;
|
||||
|
||||
public enum CommonCodingLanguages
|
||||
{
|
||||
NONE,
|
||||
|
||||
BASH,
|
||||
BLAZOR,
|
||||
C,
|
||||
CPP,
|
||||
CSHARP,
|
||||
CSS,
|
||||
FORTRAN,
|
||||
GDSCRIPT,
|
||||
GO,
|
||||
HTML,
|
||||
JAVA,
|
||||
JAVASCRIPT,
|
||||
JSON,
|
||||
JULIA,
|
||||
KOTLIN,
|
||||
LUA,
|
||||
MARKDOWN,
|
||||
MATHEMATICA,
|
||||
MATLAB,
|
||||
PHP,
|
||||
POWERSHELL,
|
||||
PROLOG,
|
||||
PYTHON,
|
||||
R,
|
||||
RUBY,
|
||||
RUST,
|
||||
SQL,
|
||||
SWIFT,
|
||||
TYPESCRIPT,
|
||||
XML,
|
||||
|
||||
OTHER,
|
||||
}
|
||||
@@ -31,7 +31,7 @@ public partial class Home : ComponentBase
|
||||
private static readonly TextItem[] ITEMS_ADVANTAGES =
|
||||
[
|
||||
new TextItem("Free of charge", "The app is free to use, both for personal and commercial purposes."),
|
||||
new TextItem("Independence", "You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT4o etc.), Mistral, Anthropic (Claude), and self-hosted models using llama.cpp or LM Studio. Support for Google Gemini, ollama, Replicate, and Fireworks is planned."),
|
||||
new TextItem("Independence", "You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT4o etc.), Mistral, Anthropic (Claude), and self-hosted models using llama.cpp, ollama, or LM Studio. Support for Google Gemini, Replicate, and Fireworks is planned."),
|
||||
new TextItem("Unrestricted usage", "Unlike services like ChatGPT, which impose limits after intensive use, MindWork AI Studio offers unlimited usage through the providers API."),
|
||||
new TextItem("Cost-effective", "You only pay for what you use, which can be cheaper than monthly subscription services like ChatGPT Plus, especially if used infrequently. But beware, here be dragons: For extremely intensive usage, the API costs can be significantly higher. Unfortunately, providers currently do not offer a way to display current costs in the app. Therefore, check your account with the respective provider to see how your costs are developing. When available, use prepaid and set a cost limit."),
|
||||
new TextItem("Privacy", "The data entered into the app is not used for training by the providers since we are using the provider's API."),
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
@using AIStudio.Settings
|
||||
@inherits AssistantBaseCore
|
||||
|
||||
<MudTextField T="string" @bind-Text="@this.inputContext" Validation="@this.ValidatingContext" AdornmentIcon="@Icons.Material.Filled.Description" Adornment="Adornment.Start" Label="Your context" Variant="Variant.Outlined" Lines="3" AutoGrow="@true" MaxLines="12" Class="mb-3"/>
|
||||
<MudTextField T="string" @bind-Text="@this.inputContext" Validation="@this.ValidatingContext" AdornmentIcon="@Icons.Material.Filled.Description" Adornment="Adornment.Start" Label="Your context" Variant="Variant.Outlined" Lines="3" AutoGrow="@true" MaxLines="12" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
|
||||
|
||||
<MudStack Row="@true" AlignItems="AlignItems.Center" Class="mb-3">
|
||||
<MudSelect T="IconSources" @bind-Value="@this.selectedIconSource" AdornmentIcon="@Icons.Material.Filled.Source" Adornment="Adornment.Start" Label="Your icon source" Variant="Variant.Outlined" Margin="Margin.Dense">
|
||||
@@ -17,7 +17,7 @@
|
||||
}
|
||||
</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)
|
||||
{
|
||||
<MudSelectItem Value="@provider"/>
|
||||
|
||||
@@ -35,14 +35,6 @@ public partial class AssistantIconFinder : AssistantBaseCore
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? ValidatingProvider(AIStudio.Settings.Provider provider)
|
||||
{
|
||||
if(provider.UsedProvider == Providers.NONE)
|
||||
return "Please select a provider.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task FindIcon()
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@page "/settings"
|
||||
@using AIStudio.Provider
|
||||
@using Host = AIStudio.Provider.SelfHosted.Host
|
||||
|
||||
<MudText Typo="Typo.h3" Class="mb-12">Settings</MudText>
|
||||
|
||||
@@ -26,10 +27,18 @@
|
||||
<MudTd>@context.InstanceName</MudTd>
|
||||
<MudTd>@context.UsedProvider</MudTd>
|
||||
<MudTd>
|
||||
@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")
|
||||
}
|
||||
</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.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.IsSelfHosted, provider.IsSelfHosted },
|
||||
{ x => x.IsEditing, true },
|
||||
{ x => x.DataHost, provider.Host },
|
||||
};
|
||||
|
||||
var dialogReference = await this.DialogService.ShowAsync<ProviderDialog>("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)
|
||||
{
|
||||
|
||||
+4
-4
@@ -3,7 +3,7 @@
|
||||
@using AIStudio.Tools
|
||||
@inherits AssistantBaseCore
|
||||
|
||||
<MudTextField T="string" @bind-Text="@this.inputText" Validation="@this.ValidatingText" AdornmentIcon="@Icons.Material.Filled.DocumentScanner" Adornment="Adornment.Start" Label="Your input" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3"/>
|
||||
<MudTextField T="string" @bind-Text="@this.inputText" Validation="@this.ValidatingText" AdornmentIcon="@Icons.Material.Filled.DocumentScanner" Adornment="Adornment.Start" Label="Your input" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
|
||||
|
||||
<MudStack Row="@true" AlignItems="AlignItems.Center" Class="mb-3">
|
||||
<MudSelect T="CommonLanguages" @bind-Value="@this.selectedTargetLanguage" AdornmentIcon="@Icons.Material.Filled.Translate" Adornment="Adornment.Start" Label="Target language" Variant="Variant.Outlined" Margin="Margin.Dense">
|
||||
@@ -14,7 +14,7 @@
|
||||
</MudSelect>
|
||||
@if (this.selectedTargetLanguage is CommonLanguages.OTHER)
|
||||
{
|
||||
<MudTextField T="string" @bind-Text="@this.customTargetLanguage" Validation="@this.ValidateCustomLanguage" Label="Custom target language" Variant="Variant.Outlined" Margin="Margin.Dense"/>
|
||||
<MudTextField T="string" @bind-Text="@this.customTargetLanguage" Validation="@this.ValidateCustomLanguage" Label="Custom target language" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
|
||||
}
|
||||
</MudStack>
|
||||
|
||||
@@ -27,11 +27,11 @@
|
||||
</MudSelect>
|
||||
@if (this.selectedComplexity is Complexity.SCIENTIFIC_LANGUAGE_OTHER_EXPERTS)
|
||||
{
|
||||
<MudTextField T="string" @bind-Text="@this.expertInField" Validation="@this.ValidateExpertInField" Label="Your expertise" Variant="Variant.Outlined" Margin="Margin.Dense"/>
|
||||
<MudTextField T="string" @bind-Text="@this.expertInField" Validation="@this.ValidateExpertInField" Label="Your expertise" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
|
||||
}
|
||||
</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)
|
||||
{
|
||||
<MudSelectItem Value="@provider"/>
|
||||
|
||||
-8
@@ -38,14 +38,6 @@ public partial class AssistantTextSummarizer : AssistantBaseCore
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? ValidatingProvider(AIStudio.Settings.Provider provider)
|
||||
{
|
||||
if(provider.UsedProvider == Providers.NONE)
|
||||
return "Please select a provider.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? ValidateCustomLanguage(string language)
|
||||
{
|
||||
if(this.selectedTargetLanguage == CommonLanguages.OTHER && string.IsNullOrWhiteSpace(language))
|
||||
|
||||
@@ -11,11 +11,11 @@
|
||||
|
||||
@if (this.liveTranslation)
|
||||
{
|
||||
<MudTextField T="string" @bind-Text="@this.inputText" Validation="@this.ValidatingText" AdornmentIcon="@Icons.Material.Filled.DocumentScanner" Adornment="Adornment.Start" Label="Your input" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" Immediate="@true" DebounceInterval="1_000" OnDebounceIntervalElapsed="() => this.TranslateText(force: false)"/>
|
||||
<MudTextField T="string" @bind-Text="@this.inputText" Validation="@this.ValidatingText" AdornmentIcon="@Icons.Material.Filled.DocumentScanner" Adornment="Adornment.Start" Label="Your input" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" Immediate="@true" DebounceInterval="1_000" OnDebounceIntervalElapsed="() => this.TranslateText(force: false)" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudTextField T="string" @bind-Text="@this.inputText" Validation="@this.ValidatingText" AdornmentIcon="@Icons.Material.Filled.DocumentScanner" Adornment="Adornment.Start" Label="Your input" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" />
|
||||
<MudTextField T="string" @bind-Text="@this.inputText" Validation="@this.ValidatingText" AdornmentIcon="@Icons.Material.Filled.DocumentScanner" Adornment="Adornment.Start" Label="Your input" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
|
||||
}
|
||||
|
||||
<MudStack Row="@true" AlignItems="AlignItems.Center" Class="mb-3">
|
||||
@@ -34,11 +34,11 @@ else
|
||||
</MudSelect>
|
||||
@if (this.selectedTargetLanguage is CommonLanguages.OTHER)
|
||||
{
|
||||
<MudTextField T="string" @bind-Text="@this.customTargetLanguage" Validation="@this.ValidateCustomLanguage" Label="Custom target language" Variant="Variant.Outlined" Margin="Margin.Dense"/>
|
||||
<MudTextField T="string" @bind-Text="@this.customTargetLanguage" Validation="@this.ValidateCustomLanguage" Label="Custom target language" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
|
||||
}
|
||||
</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)
|
||||
{
|
||||
<MudSelectItem Value="@provider"/>
|
||||
|
||||
@@ -34,14 +34,6 @@ public partial class AssistantTranslator : AssistantBaseCore
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? ValidatingProvider(AIStudio.Settings.Provider provider)
|
||||
{
|
||||
if(provider.UsedProvider == Providers.NONE)
|
||||
return "Please select a provider.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? ValidatingTargetLanguage(CommonLanguages language)
|
||||
{
|
||||
if(language == CommonLanguages.AS_IS)
|
||||
|
||||
@@ -45,17 +45,15 @@ public static class ExtensionsProvider
|
||||
/// <summary>
|
||||
/// Creates a new provider instance based on the provider value.
|
||||
/// </summary>
|
||||
/// <param name="provider">The provider value.</param>
|
||||
/// <param name="instanceName">The used instance name.</param>
|
||||
/// <param name="hostname">The hostname of the provider.</param>
|
||||
/// <param name="providerSettings">The provider settings.</param>
|
||||
/// <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.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(),
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
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<IEnumerable<Provider.Model>> 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<ModelsResponse>(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<ModelsResponse>(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
|
||||
|
||||
@@ -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.
|
||||
/// </summary>
|
||||
public Version Version { get; init; } = Version.V2;
|
||||
public Version Version { get; init; } = Version.V3;
|
||||
|
||||
/// <summary>
|
||||
/// List of configured providers.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using AIStudio.Provider;
|
||||
|
||||
using Host = AIStudio.Provider.SelfHosted.Host;
|
||||
|
||||
namespace AIStudio.Settings;
|
||||
|
||||
/// <summary>
|
||||
@@ -12,7 +14,15 @@ namespace AIStudio.Settings;
|
||||
/// <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="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
|
||||
|
||||
@@ -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})";
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@using AIStudio.Provider
|
||||
@using AIStudio.Provider.SelfHosted
|
||||
@using MudBlazor
|
||||
|
||||
<MudDialog>
|
||||
@@ -39,11 +40,19 @@
|
||||
AdornmentIcon="@Icons.Material.Filled.Dns"
|
||||
AdornmentColor="Color.Info"
|
||||
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">
|
||||
<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">
|
||||
<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">
|
||||
@foreach (var model in this.availableModels)
|
||||
{
|
||||
<MudSelectItem Value="@model">@model</MudSelectItem>
|
||||
@@ -61,7 +70,7 @@
|
||||
AdornmentIcon="@Icons.Material.Filled.Lightbulb"
|
||||
AdornmentColor="Color.Info"
|
||||
Validation="@this.ValidatingInstanceName"
|
||||
UserAttributes="@INSTANCE_NAME_ATTRIBUTES"
|
||||
UserAttributes="@SPELLCHECK_ATTRIBUTES"
|
||||
/>
|
||||
|
||||
</MudForm>
|
||||
|
||||
@@ -4,6 +4,8 @@ using AIStudio.Provider;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
using Host = AIStudio.Provider.SelfHosted.Host;
|
||||
|
||||
namespace AIStudio.Settings;
|
||||
|
||||
/// <summary>
|
||||
@@ -38,6 +40,12 @@ public partial class ProviderDialog : ComponentBase
|
||||
[Parameter]
|
||||
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>
|
||||
/// Is this provider self-hosted?
|
||||
/// </summary>
|
||||
@@ -68,7 +76,7 @@ public partial class ProviderDialog : ComponentBase
|
||||
[Inject]
|
||||
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>
|
||||
/// 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<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
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -10,4 +10,5 @@ public enum Version
|
||||
|
||||
V1,
|
||||
V2,
|
||||
V3,
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
# v0.8.1, build 163 (2024-07-16 08:32 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
|
||||
@@ -0,0 +1,3 @@
|
||||
# v0.8.2, build 164 (2024-07-16 18:03 UTC)
|
||||
- Added the coding assistant.
|
||||
- Fixed the in-app description of possible self-hosted models.
|
||||
+4
-4
@@ -1,9 +1,9 @@
|
||||
0.8.0
|
||||
2024-07-14 19:39:09 UTC
|
||||
162
|
||||
0.8.2
|
||||
2024-07-16 18:03:04 UTC
|
||||
164
|
||||
8.0.107 (commit 1bdaef7265)
|
||||
8.0.7 (commit 2aade6beb0)
|
||||
1.79.0 (commit 129f3b996)
|
||||
6.20.0
|
||||
1.6.1
|
||||
ba6610bd964, release
|
||||
c92ce49af2d, release
|
||||
Generated
+1
-1
@@ -2313,7 +2313,7 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "mindwork-ai-studio"
|
||||
version = "0.8.0"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"arboard",
|
||||
"flexi_logger",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mindwork-ai-studio"
|
||||
version = "0.8.0"
|
||||
version = "0.8.2"
|
||||
edition = "2021"
|
||||
description = "MindWork AI Studio"
|
||||
authors = ["Thorsten Sommer"]
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
},
|
||||
"package": {
|
||||
"productName": "MindWork AI Studio",
|
||||
"version": "0.8.0"
|
||||
"version": "0.8.2"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
|
||||
Reference in new issue
Block a user