mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-20 15:38:00 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7250b270f5 | ||
|
|
d437d08640 | ||
|
|
61f7a3dd72 | ||
|
|
55d7895f58 | ||
|
|
e2859b3d76 | ||
|
|
0a951ead3e | ||
|
|
432ae30fdf | ||
|
|
799112eb9d | ||
|
|
f806ee28c5 | ||
|
|
32ff143b21 | ||
|
|
90a1f681c5 | ||
|
|
99fe9f3a4f | ||
|
|
cf8a9800f3 | ||
|
|
f57e971f80 | ||
|
|
0d104f5abc | ||
|
|
ba6b8d2b3c | ||
|
|
1c72c4d6ec | ||
|
|
4ca5aba58f | ||
|
|
c4d2574508 | ||
|
|
ceac9cefe5 | ||
|
|
ebe2ad62c6 | ||
|
|
9c0dea9927 | ||
|
|
d2a2d658e6 | ||
|
|
d82427ba56 | ||
|
|
386121d198 | ||
|
|
f9245d52c2 | ||
|
|
9fa2a1a135 |
No files matched your search
@@ -7,18 +7,22 @@ Things we are currently working on:
|
||||
|
||||
- [x] ~~Runtime: Restructuring the code into meaningful modules (PR [#192](https://github.com/MindWorkAI/AI-Studio/pull/192))~~
|
||||
- [x] ~~Define the [External Data API (EDI)](https://github.com/MindWorkAI/EDI) as a contract for integrating arbitrary external data (PR [#1](https://github.com/MindWorkAI/EDI/pull/1))~~
|
||||
- [ ] App: Metadata for providers (which provider offers embeddings?)
|
||||
- [ ] App: Management of data sources (local data)
|
||||
- [x] ~~App: Metadata for providers (which provider offers embeddings?) (PR [#205](https://github.com/MindWorkAI/AI-Studio/pull/205))~~
|
||||
- [x] ~~App: Add an option to show preview features (PR [#222](https://github.com/MindWorkAI/AI-Studio/pull/222))~~
|
||||
- [x] ~~App: Configure embedding providers (PR [#224](https://github.com/MindWorkAI/AI-Studio/pull/224))~~
|
||||
- [ ] App: Management of data sources (local & external data via [EDI](https://github.com/MindWorkAI/EDI))
|
||||
- [ ] Runtime: Extract data from txt / md / pdf / docx / xlsx files
|
||||
- [ ] App: Implement embedding providers
|
||||
- [ ] App: Implement the process to vectorize local data using embeddings
|
||||
- [ ] (*Optional*) Runtime: Implement internal embedding provider through [fastembed-rs](https://github.com/Anush008/fastembed-rs)
|
||||
- [ ] App: Implement external embedding providers
|
||||
- [ ] App: Implement the process to vectorize one local file using embeddings
|
||||
- [ ] Runtime: Integration of the vector database [LanceDB](https://github.com/lancedb/lancedb)
|
||||
- [ ] App: Define an interface for the integration of RAG processes in chats
|
||||
- [ ] App: Implement the continuous process of vectorizing data
|
||||
- [ ] App: Define a common retrieval context interface for the integration of RAG processes in chats
|
||||
- [ ] App: Define a common augmentation interface for the integration of RAG processes in chats
|
||||
- [ ] App: Integrate data sources in chats
|
||||
- [ ] App: Management of data sources (external data via [EDI](https://github.com/MindWorkAI/EDI))
|
||||
|
||||
|
||||
- Since September 2024: Experiments have been started on how we can work on long texts with AI Studio. Let's say you want to write a fantasy novel or create a complex project proposal and use LLM for support. The initial experiments were promising, but not yet satisfactory. We are testing further approaches until a satisfactory solution is found. Related PR: [#167](https://github.com/MindWorkAI/AI-Studio/pull/167).
|
||||
- Since September 2024: Experiments have been started on how we can work on long texts with AI Studio. Let's say you want to write a fantasy novel or create a complex project proposal and use LLM for support. The initial experiments were promising, but not yet satisfactory. We are testing further approaches until a satisfactory solution is found. The current state of our experiment is available as an experimental preview feature through your app configuration. Related PR: ~~[#167](https://github.com/MindWorkAI/AI-Studio/pull/167), [#226](https://github.com/MindWorkAI/AI-Studio/pull/226)~~.
|
||||
|
||||
|
||||
Other News:
|
||||
@@ -30,13 +34,14 @@ Other News:
|
||||
|
||||
Features we have recently released:
|
||||
|
||||
- v0.9.22: Added options for preview features; added embedding provider configuration for RAG (preview) and writer mode (experimental preview).
|
||||
- v0.9.18: Added the new Anthropic Heiku model; added Groq and Google Gemini as provider options.
|
||||
- v0.9.17: Added the new Anthropic model `claude-3-5-sonnet-20241022`.
|
||||
- v0.9.16: Added workspace display options & improved the layout of the app window.
|
||||
- v0.9.15: Added the bias-of-the-day assistant. Tells you about a cognitive bias every day.
|
||||
- v0.9.13: You can use `ollama` providers secured with API keys.
|
||||
- v0.9.12: Added a job posting assistant to the business category and improved grammar & spelling check and rewrite assistants.
|
||||
- v0.9.11: Added enforcement of minimal confidence levels & dark mode.
|
||||
- v0.9.10: Added support for the OpenAI `o1` model.
|
||||
|
||||
## What is AI Studio?
|
||||
|
||||
@@ -47,7 +52,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**: You are not tied to any single provider. Instead, you 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), [LM Studio](https://lmstudio.ai/), or [Fireworks](https://fireworks.ai/). Support for Google Gemini, and [Replicate](https://replicate.com/) is planned.
|
||||
- **Independence**: You are not tied to any single provider. Instead, you can choose the provider that best suits their needs. Right now, we support OpenAI (GPT4o etc.), Mistral, Anthropic (Claude), Google Gemini, and self-hosted models using [llama.cpp](https://github.com/ggerganov/llama.cpp), [ollama](https://github.com/ollama/ollama), [LM Studio](https://lmstudio.ai/), [Groq](https://groq.com/), or [Fireworks](https://fireworks.ai/).
|
||||
- **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.
|
||||
@@ -71,11 +76,13 @@ To view all available tiers, please visit our [GitHub Sponsors page](https://git
|
||||
Your support, whether big or small, keeps the wheels turning and is deeply appreciated ❤️.
|
||||
|
||||
## Planned Features
|
||||
Here's an exciting look at some of the features we're planning to add to MindWork AI Studio in future releases:
|
||||
- **More providers**: We plan to add support for additional LLM providers, such as Google Gemini, giving you more options to choose from.
|
||||
- **System prompts**: Integration of a system prompt library will allow you to control the behavior of the LLM with predefined prompts, ensuring consistency and efficiency.
|
||||
- **Text replacement for better privacy**: Define keywords that will be replaced in your chats before sending content to the provider, enhancing your privacy.
|
||||
- **Advanced interactions**: We're full of ideas for advanced interactions tailored for specific use cases, whether in a business context or for writers and other professionals.
|
||||
Here's an exciting look at some of the features we're planning to add to AI Studio in future releases:
|
||||
- **Integrating your data**: You should be able to integrate your data into AI Studio. For example, your PDF or Office files, or your Markdown notes.
|
||||
- **Integration of enterprise data:** Soon, it will also be possible to integrate data from the corporate network using an interface that we have specified ([External Data API](https://github.com/MindWorkAI/EDI), EDI for short).
|
||||
- **Writing mode:** We want to integrate a writing mode that should support you in creating extensive works. We are thinking of comprehensive project proposals, tenders, or your next fantasy novel.
|
||||
- **Browser usage:** We're trying to offer the features from AI Studio to you in the browser via a plugin, so we could use spell-checking or rewriting text directly in the browser.
|
||||
- **Voice control:** You should be able to interact with the AI systems using your voice as well. To achieve this, we want to integrate voice input (speech-to-text) and output (text-to-speech). However, later on, it should also have a natural conversation flow, i.e., seamless conversation.
|
||||
- **Email monitoring:** You should have the option to connect your email inboxes with AI Studio. The AI reads your emails and sends you a notification when something important happens. At the same time, you can access knowledge from your emails in your chats.
|
||||
|
||||
Stay tuned for more updates and enhancements to make MindWork AI Studio even more powerful and versatile 🤩.
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||
<link href="system/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
|
||||
<link href="system/MudBlazor.Markdown/MudBlazor.Markdown.min.css" rel="stylesheet" />
|
||||
<link href="system/CodeBeam.MudBlazor.Extensions/MudExtensions.min.css" rel="stylesheet" />
|
||||
<link href="app.css" rel="stylesheet" />
|
||||
<HeadOutlet/>
|
||||
<script src="diff.js"></script>
|
||||
@@ -24,6 +25,7 @@
|
||||
<script src="boot.js"></script>
|
||||
<script src="system/MudBlazor/MudBlazor.min.js"></script>
|
||||
<script src="system/MudBlazor.Markdown/MudBlazor.Markdown.min.js"></script>
|
||||
<script src="system/CodeBeam.MudBlazor.Extensions/MudExtensions.min.js"></script>
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -166,6 +166,7 @@ public abstract partial class AssistantBase : ComponentBase, IMessageBusReceiver
|
||||
{
|
||||
this.chatThread = new()
|
||||
{
|
||||
SelectedProvider = this.providerSettings.Id,
|
||||
WorkspaceId = Guid.Empty,
|
||||
ChatId = Guid.NewGuid(),
|
||||
Name = string.Empty,
|
||||
@@ -185,6 +186,7 @@ public abstract partial class AssistantBase : ComponentBase, IMessageBusReceiver
|
||||
var chatId = Guid.NewGuid();
|
||||
this.chatThread = new()
|
||||
{
|
||||
SelectedProvider = this.providerSettings.Id,
|
||||
WorkspaceId = workspaceId,
|
||||
ChatId = chatId,
|
||||
Name = name,
|
||||
@@ -235,8 +237,13 @@ public abstract partial class AssistantBase : ComponentBase, IMessageBusReceiver
|
||||
Role = ChatRole.AI,
|
||||
Content = aiText,
|
||||
};
|
||||
|
||||
this.chatThread?.Blocks.Add(this.resultingContentBlock);
|
||||
|
||||
if (this.chatThread is not null)
|
||||
{
|
||||
this.chatThread.Blocks.Add(this.resultingContentBlock);
|
||||
this.chatThread.SelectedProvider = this.providerSettings.Id;
|
||||
}
|
||||
|
||||
this.isProcessing = true;
|
||||
this.StateHasChanged();
|
||||
|
||||
@@ -284,7 +291,9 @@ public abstract partial class AssistantBase : ComponentBase, IMessageBusReceiver
|
||||
switch (destination)
|
||||
{
|
||||
case Tools.Components.CHAT:
|
||||
MessageBus.INSTANCE.DeferMessage(this, sendToData.Event, this.ConvertToChatThread);
|
||||
var convertedChatThread = this.ConvertToChatThread;
|
||||
convertedChatThread = convertedChatThread with { SelectedProvider = this.providerSettings.Id };
|
||||
MessageBus.INSTANCE.DeferMessage(this, sendToData.Event, convertedChatThread);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
@@ -128,7 +128,7 @@ public partial class BiasOfTheDayAssistant : AssistantBaseCore
|
||||
ChatId = this.SettingsManager.ConfigurationData.BiasOfTheDay.BiasOfTheDayChatId,
|
||||
};
|
||||
|
||||
if (Workspaces.IsChatExisting(biasChat))
|
||||
if (WorkspaceBehaviour.IsChatExisting(biasChat))
|
||||
{
|
||||
MessageBus.INSTANCE.DeferMessage(this, Event.LOAD_CHAT, biasChat);
|
||||
this.NavigationManager.NavigateTo(Routes.CHAT);
|
||||
|
||||
@@ -15,6 +15,11 @@ public sealed record ChatThread
|
||||
/// </summary>
|
||||
public Guid WorkspaceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the provider selected for the chat thread.
|
||||
/// </summary>
|
||||
public string SelectedProvider { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The name of the chat thread. Usually generated by an AI model or manually edited by the user.
|
||||
/// </summary>
|
||||
|
||||
@@ -40,7 +40,7 @@ public sealed class ContentText : IContent
|
||||
if(chatThread is null)
|
||||
return;
|
||||
|
||||
// Store the last time we got a response. We use this ater
|
||||
// Store the last time we got a response. We use this later
|
||||
// to determine whether we should notify the UI about the
|
||||
// new content or not. Depends on the energy saving mode
|
||||
// the user chose.
|
||||
|
||||
@@ -13,6 +13,10 @@ public partial class Changelog
|
||||
|
||||
public static readonly Log[] LOGS =
|
||||
[
|
||||
new (197, "v0.9.22, build 197 (2024-12-04 10:58 UTC)", "v0.9.22.md"),
|
||||
new (196, "v0.9.21, build 196 (2024-11-23 12:22 UTC)", "v0.9.21.md"),
|
||||
new (195, "v0.9.20, build 195 (2024-11-16 20:44 UTC)", "v0.9.20.md"),
|
||||
new (194, "v0.9.19, build 194 (2024-11-14 05:58 UTC)", "v0.9.19.md"),
|
||||
new (193, "v0.9.18, build 193 (2024-11-09 21:10 UTC)", "v0.9.18.md"),
|
||||
new (192, "v0.9.17, build 192 (2024-11-03 11:11 UTC)", "v0.9.17.md"),
|
||||
new (191, "v0.9.16, build 191 (2024-11-02 22:04 UTC)", "v0.9.16.md"),
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
@inherits ConfigurationBase
|
||||
@typeparam T
|
||||
|
||||
<MudSelectExtended
|
||||
T="T"
|
||||
MultiSelection="@true"
|
||||
MultiSelectionTextFunc="@this.GetMultiSelectionText"
|
||||
SelectedValues="@this.SelectedValues()"
|
||||
Strict="@true"
|
||||
Disabled="@this.Disabled()"
|
||||
Margin="Margin.Dense"
|
||||
Label="@this.OptionDescription"
|
||||
Class="@GetClass"
|
||||
Variant="Variant.Outlined"
|
||||
HelperText="@this.OptionHelp"
|
||||
SelectedValuesChanged="@this.OptionChanged">
|
||||
@foreach (var data in this.Data)
|
||||
{
|
||||
<MudSelectItemExtended Value="@data.Value">
|
||||
@data.Name
|
||||
</MudSelectItemExtended>
|
||||
}
|
||||
</MudSelectExtended>
|
||||
@@ -0,0 +1,54 @@
|
||||
using AIStudio.Settings;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration component for selecting many values from a list.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value to select.</typeparam>
|
||||
public partial class ConfigurationMultiSelect<T> : ConfigurationBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The data to select from.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public IEnumerable<ConfigurationSelectData<T>> Data { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// The selected values.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Func<HashSet<T>> SelectedValues { get; set; } = () => [];
|
||||
|
||||
/// <summary>
|
||||
/// An action that is called when the selection changes.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Action<HashSet<T>> SelectionUpdate { get; set; } = _ => { };
|
||||
|
||||
private async Task OptionChanged(IEnumerable<T?>? updatedValues)
|
||||
{
|
||||
if(updatedValues is null)
|
||||
this.SelectionUpdate([]);
|
||||
else
|
||||
this.SelectionUpdate(updatedValues.Where(n => n is not null).ToHashSet()!);
|
||||
|
||||
await this.SettingsManager.StoreSettings();
|
||||
await this.InformAboutChange();
|
||||
}
|
||||
|
||||
private static string GetClass => $"{MARGIN_CLASS} rounded-lg";
|
||||
|
||||
private string GetMultiSelectionText(List<T?>? selectedValues)
|
||||
{
|
||||
if(selectedValues is null || selectedValues.Count == 0)
|
||||
return "No preview features selected.";
|
||||
|
||||
if(selectedValues.Count == 1)
|
||||
return $"You have selected 1 preview feature.";
|
||||
|
||||
return $"You have selected {selectedValues.Count} preview features.";
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
@inherits ConfigurationBase
|
||||
@typeparam T
|
||||
|
||||
<MudSelect T="T" Value="@this.SelectedValue()" Strict="@true" Disabled="@this.Disabled()" Margin="Margin.Dense" Label="@this.OptionDescription" Class="@GetClass" Variant="Variant.Outlined" HelperText="@this.OptionHelp" ValueChanged="@this.OptionChanged">
|
||||
<MudSelect T="T" Value="@this.SelectedValue()" Strict="@true" ShrinkLabel="@true" Disabled="@this.Disabled()" Margin="Margin.Dense" Label="@this.OptionDescription" Class="@GetClass" Variant="Variant.Outlined" HelperText="@this.OptionHelp" ValueChanged="@this.OptionChanged">
|
||||
@foreach (var data in this.Data)
|
||||
{
|
||||
<MudSelectItem Value="@data.Value">
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
using AIStudio.Settings;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Components;
|
||||
|
||||
public abstract class MSGComponentBase : ComponentBase, IDisposable, IMessageBusReceiver
|
||||
{
|
||||
[Inject]
|
||||
protected SettingsManager SettingsManager { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
protected MessageBus MessageBus { get; init; } = null!;
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<MudTooltip Placement="Placement.Bottom" Arrow="@true">
|
||||
<ChildContent>
|
||||
<MudChip T="string" Icon="@Icons.Material.Filled.FirstPage" Color="Color.Error" Class="mb-3">
|
||||
Alpha
|
||||
</MudChip>
|
||||
</ChildContent>
|
||||
<TooltipContent>
|
||||
<div style="max-width: 22em;">
|
||||
<MudText Typo="Typo.body2" Class="mb-3">
|
||||
This feature is currently in the alpha phase.
|
||||
Expect bugs and unfinished work.
|
||||
</MudText>
|
||||
|
||||
<MudText Typo="Typo.body2">
|
||||
Alpha phase means that we are working on the
|
||||
last details before the beta phase.
|
||||
</MudText>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</MudTooltip>
|
||||
@@ -0,0 +1,5 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Components;
|
||||
|
||||
public partial class PreviewAlpha : ComponentBase;
|
||||
@@ -0,0 +1,19 @@
|
||||
<MudTooltip Placement="Placement.Bottom" Arrow="@true">
|
||||
<ChildContent>
|
||||
<MudChip T="string" Icon="@Icons.Material.Filled.HourglassTop" Color="Color.Info" Class="mb-3">
|
||||
Beta
|
||||
</MudChip>
|
||||
</ChildContent>
|
||||
<TooltipContent>
|
||||
<div style="max-width: 20em;">
|
||||
<MudText Typo="Typo.body2" Class="mb-3">
|
||||
This feature is currently in the beta phase.
|
||||
It is still be possible that there are some bugs.
|
||||
</MudText>
|
||||
|
||||
<MudText Typo="Typo.body2">
|
||||
Beta phase means that we are testing the feature.
|
||||
</MudText>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</MudTooltip>
|
||||
@@ -0,0 +1,5 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Components;
|
||||
|
||||
public partial class PreviewBeta : ComponentBase;
|
||||
@@ -0,0 +1,22 @@
|
||||
<MudTooltip Placement="Placement.Bottom" Arrow="@true">
|
||||
<ChildContent>
|
||||
<MudChip T="string" Icon="@Icons.Material.Filled.Science" Color="Color.Error" Class="mb-3">
|
||||
Experimental
|
||||
</MudChip>
|
||||
</ChildContent>
|
||||
<TooltipContent>
|
||||
<div style="max-width: 26em;">
|
||||
<MudText Typo="Typo.body2" Class="mb-3">
|
||||
This feature is currently in the experimental phase.
|
||||
Expect bugs, unfinished work, changes in future
|
||||
versions, and more.
|
||||
</MudText>
|
||||
|
||||
<MudText Typo="Typo.body2">
|
||||
Experimental phase means that we have a vision for a feature
|
||||
but not a clear plan yet. We are still exploring the
|
||||
possibilities.
|
||||
</MudText>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</MudTooltip>
|
||||
@@ -0,0 +1,5 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Components;
|
||||
|
||||
public partial class PreviewExperimental : ComponentBase;
|
||||
@@ -0,0 +1,21 @@
|
||||
<MudTooltip Placement="Placement.Bottom" Arrow="@true">
|
||||
<ChildContent>
|
||||
<MudChip T="string" Icon="@Icons.Material.Filled.HourglassBottom" Color="Color.Error" Class="mb-3">
|
||||
Prototype
|
||||
</MudChip>
|
||||
</ChildContent>
|
||||
<TooltipContent>
|
||||
<div style="max-width: 22em;">
|
||||
<MudText Typo="Typo.body2" Class="mb-3">
|
||||
This feature is currently in the prototype phase.
|
||||
Expect bugs, unfinished work, changes in future
|
||||
versions, and more.
|
||||
</MudText>
|
||||
|
||||
<MudText Typo="Typo.body2">
|
||||
Prototype phase means that we have a plan but we
|
||||
are still working on it.
|
||||
</MudText>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</MudTooltip>
|
||||
@@ -0,0 +1,5 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Components;
|
||||
|
||||
public partial class PreviewPrototype : ComponentBase;
|
||||
@@ -0,0 +1,19 @@
|
||||
<MudTooltip Placement="Placement.Bottom" Arrow="@true">
|
||||
<ChildContent>
|
||||
<MudChip T="string" Icon="@Icons.Material.Filled.VerifiedUser" Color="Color.Success" Class="mb-3">
|
||||
Release Candidate
|
||||
</MudChip>
|
||||
</ChildContent>
|
||||
<TooltipContent>
|
||||
<div style="max-width: 20em;">
|
||||
<MudText Typo="Typo.body2" Class="mb-3">
|
||||
This feature is about to be released. We think it's ready for production.
|
||||
There should be no more bugs.
|
||||
</MudText>
|
||||
|
||||
<MudText Typo="Typo.body2">
|
||||
Release candidates are the final step before a feature is proven to be stable.
|
||||
</MudText>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</MudTooltip>
|
||||
@@ -0,0 +1,5 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Components;
|
||||
|
||||
public partial class PreviewReleaseCandidate : ComponentBase;
|
||||
@@ -6,11 +6,11 @@ public partial class Vision : ComponentBase
|
||||
{
|
||||
private static readonly TextItem[] ITEMS_VISION =
|
||||
[
|
||||
new TextItem("Meet your needs", "Whatever your job or task is, MindWork AI Studio aims to meet your needs: whether you're a project manager, scientist, artist, author, software developer, or game developer."),
|
||||
new TextItem("One stop shop", "The app will strive to fulfill all your AI needs: text-generation AI (LLM), image-generation AI, audio-generation AI (text-to-speech, potentially even text-to-music), and audio input (transcription, dictation). When there's a provider and an API available, we'll try to integrate it."),
|
||||
new TextItem("Local file system", "When you wish, we aim to integrate your local system. Local documents could be incorporated using Retrieval-Augmented Generation (RAG), and we could directly save AI-generated content to your file system."),
|
||||
new TextItem("Local AI systems", "Want to use AI systems locally and offline? We aim to make that possible too."),
|
||||
new TextItem("Your AI systems", "Prefer to run your AI systems with providers like replicate.com? We plan to support that!"),
|
||||
new TextItem("Assistants", "We aim to integrate specialized user interfaces as assistants. For example, a UI specifically for writing emails, or one designed for translating and correcting text, and more."),
|
||||
new("Meet your needs", "Whatever your job or task is, MindWork AI Studio aims to meet your needs: whether you're a project manager, scientist, artist, author, software developer, or game developer."),
|
||||
new("One stop shop", "The app will strive to fulfill all your AI needs: text-generation AI (LLM), image-generation AI, audio-generation AI (text-to-speech, potentially even text-to-music), and audio input (transcription, dictation). When there's a provider and an API available, we'll try to integrate it."),
|
||||
new("Local file system", "When you wish, we aim to integrate your local system. Local documents could be incorporated using Retrieval-Augmented Generation (RAG), and we could directly save AI-generated content to your file system."),
|
||||
new("Local AI systems", "Want to use AI systems locally and offline? We aim to make that possible too."),
|
||||
new("Your AI systems", "Prefer to run your AI systems with providers like replicate.com? We plan to support that!"),
|
||||
new("Assistants", "We aim to integrate specialized user interfaces as assistants. For example, a UI specifically for writing emails, or one designed for translating and correcting text, and more."),
|
||||
];
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Dialogs;
|
||||
@@ -41,18 +40,6 @@ public partial class Workspaces : ComponentBase
|
||||
private const Placement WORKSPACE_ITEM_TOOLTIP_PLACEMENT = Placement.Bottom;
|
||||
|
||||
public static readonly Guid WORKSPACE_ID_BIAS = Guid.Parse("82050a4e-ee92-43d7-8ee5-ab512f847e02");
|
||||
private static readonly JsonSerializerOptions JSON_OPTIONS = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
AllowTrailingCommas = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
|
||||
PropertyNameCaseInsensitive = true,
|
||||
Converters =
|
||||
{
|
||||
new JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseUpper),
|
||||
}
|
||||
};
|
||||
|
||||
private readonly List<TreeItemData<ITreeItem>> treeItems = new();
|
||||
|
||||
@@ -160,16 +147,6 @@ public partial class Workspaces : ComponentBase
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<string> LoadWorkspaceName(Guid workspaceId)
|
||||
{
|
||||
if(workspaceId == Guid.Empty)
|
||||
return string.Empty;
|
||||
|
||||
var workspacePath = Path.Join(SettingsManager.DataDirectory, "workspaces", workspaceId.ToString());
|
||||
var workspaceNamePath = Path.Join(workspacePath, "name");
|
||||
return await File.ReadAllTextAsync(workspaceNamePath, Encoding.UTF8);
|
||||
}
|
||||
|
||||
private async Task<IReadOnlyCollection<TreeItemData<ITreeItem>>> LoadWorkspaces()
|
||||
{
|
||||
var workspaces = new List<TreeItemData<ITreeItem>>();
|
||||
@@ -261,22 +238,7 @@ public partial class Workspaces : ComponentBase
|
||||
|
||||
public async Task StoreChat(ChatThread chat, bool reloadTreeItems = true)
|
||||
{
|
||||
string chatDirectory;
|
||||
if (chat.WorkspaceId == Guid.Empty)
|
||||
chatDirectory = Path.Join(SettingsManager.DataDirectory, "tempChats", chat.ChatId.ToString());
|
||||
else
|
||||
chatDirectory = Path.Join(SettingsManager.DataDirectory, "workspaces", chat.WorkspaceId.ToString(), chat.ChatId.ToString());
|
||||
|
||||
// Ensure the directory exists:
|
||||
Directory.CreateDirectory(chatDirectory);
|
||||
|
||||
// Save the chat name:
|
||||
var chatNamePath = Path.Join(chatDirectory, "name");
|
||||
await File.WriteAllTextAsync(chatNamePath, chat.Name);
|
||||
|
||||
// Save the thread as thread.json:
|
||||
var chatPath = Path.Join(chatDirectory, "thread.json");
|
||||
await File.WriteAllTextAsync(chatPath, JsonSerializer.Serialize(chat, JSON_OPTIONS), Encoding.UTF8);
|
||||
await WorkspaceBehaviour.StoreChat(chat);
|
||||
|
||||
// Reload the tree items:
|
||||
if(reloadTreeItems)
|
||||
@@ -284,24 +246,6 @@ public partial class Workspaces : ComponentBase
|
||||
|
||||
this.StateHasChanged();
|
||||
}
|
||||
|
||||
public async Task LoadChat(LoadChat loadChat)
|
||||
{
|
||||
var chatPath = loadChat.WorkspaceId == Guid.Empty
|
||||
? Path.Join(SettingsManager.DataDirectory, "tempChats", loadChat.ChatId.ToString())
|
||||
: Path.Join(SettingsManager.DataDirectory, "workspaces", loadChat.WorkspaceId.ToString(), loadChat.ChatId.ToString());
|
||||
|
||||
await this.LoadChat(chatPath, switchToChat: true);
|
||||
}
|
||||
|
||||
public static bool IsChatExisting(LoadChat loadChat)
|
||||
{
|
||||
var chatPath = loadChat.WorkspaceId == Guid.Empty
|
||||
? Path.Join(SettingsManager.DataDirectory, "tempChats", loadChat.ChatId.ToString())
|
||||
: Path.Join(SettingsManager.DataDirectory, "workspaces", loadChat.WorkspaceId.ToString(), loadChat.ChatId.ToString());
|
||||
|
||||
return Directory.Exists(chatPath);
|
||||
}
|
||||
|
||||
private async Task<ChatThread?> LoadChat(string? chatPath, bool switchToChat)
|
||||
{
|
||||
@@ -328,7 +272,7 @@ public partial class Workspaces : ComponentBase
|
||||
try
|
||||
{
|
||||
var chatData = await File.ReadAllTextAsync(Path.Join(chatPath, "thread.json"), Encoding.UTF8);
|
||||
var chat = JsonSerializer.Deserialize<ChatThread>(chatData, JSON_OPTIONS);
|
||||
var chat = JsonSerializer.Deserialize<ChatThread>(chatData, WorkspaceBehaviour.JSON_OPTIONS);
|
||||
if (switchToChat)
|
||||
{
|
||||
this.CurrentChatThread = chat;
|
||||
@@ -354,7 +298,7 @@ public partial class Workspaces : ComponentBase
|
||||
|
||||
if (askForConfirmation)
|
||||
{
|
||||
var workspaceName = await this.LoadWorkspaceName(chat.WorkspaceId);
|
||||
var workspaceName = await WorkspaceBehaviour.LoadWorkspaceName(chat.WorkspaceId);
|
||||
var dialogParameters = new DialogParameters
|
||||
{
|
||||
{
|
||||
@@ -419,7 +363,7 @@ public partial class Workspaces : ComponentBase
|
||||
return;
|
||||
|
||||
var workspaceId = Guid.Parse(Path.GetFileName(workspacePath));
|
||||
var workspaceName = await this.LoadWorkspaceName(workspaceId);
|
||||
var workspaceName = await WorkspaceBehaviour.LoadWorkspaceName(workspaceId);
|
||||
var dialogParameters = new DialogParameters
|
||||
{
|
||||
{ "Message", $"Please enter a new or edit the name for your workspace '{workspaceName}':" },
|
||||
@@ -482,7 +426,7 @@ public partial class Workspaces : ComponentBase
|
||||
return;
|
||||
|
||||
var workspaceId = Guid.Parse(Path.GetFileName(workspacePath));
|
||||
var workspaceName = await this.LoadWorkspaceName(workspaceId);
|
||||
var workspaceName = await WorkspaceBehaviour.LoadWorkspaceName(workspaceId);
|
||||
|
||||
// Determine how many chats are in the workspace:
|
||||
var chatCount = Directory.EnumerateDirectories(workspacePath).Count();
|
||||
@@ -572,7 +516,7 @@ public partial class Workspaces : ComponentBase
|
||||
ChatId = Guid.NewGuid(),
|
||||
Name = string.Empty,
|
||||
Seed = this.RNG.Next(),
|
||||
SystemPrompt = "You are a helpful assistant!",
|
||||
SystemPrompt = SystemPrompts.DEFAULT,
|
||||
Blocks = [],
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@using AIStudio
|
||||
@using AIStudio.Tools
|
||||
@using MudBlazor
|
||||
@using MudBlazor
|
||||
@using MudExtensions
|
||||
@@ -0,0 +1,118 @@
|
||||
@using AIStudio.Provider
|
||||
@using AIStudio.Provider.SelfHosted
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudForm @ref="@this.form" @bind-IsValid="@this.dataIsValid" @bind-Errors="@this.dataIssues">
|
||||
<MudStack Row="@true" AlignItems="AlignItems.Center">
|
||||
@* ReSharper disable once CSharpWarnings::CS8974 *@
|
||||
<MudSelect @bind-Value="@this.DataLLMProvider" Label="Provider" Class="mb-3" OpenIcon="@Icons.Material.Filled.AccountBalance" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingProvider">
|
||||
@foreach (LLMProviders provider in Enum.GetValues(typeof(LLMProviders)))
|
||||
{
|
||||
if (provider.ProvideEmbeddings())
|
||||
{
|
||||
<MudSelectItem Value="@provider">@provider</MudSelectItem>
|
||||
}
|
||||
}
|
||||
</MudSelect>
|
||||
<MudButton Disabled="@(!this.DataLLMProvider.ShowRegisterButton())" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.DataLLMProvider.GetCreationURL()" Target="_blank">Create account</MudButton>
|
||||
</MudStack>
|
||||
|
||||
@* ReSharper disable once CSharpWarnings::CS8974 *@
|
||||
<MudTextField
|
||||
T="string"
|
||||
@bind-Text="@this.dataAPIKey"
|
||||
Label="@this.APIKeyText"
|
||||
Disabled="@(!this.DataLLMProvider.IsAPIKeyNeeded(this.DataHost))"
|
||||
Class="mb-3"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.VpnKey"
|
||||
AdornmentColor="Color.Info"
|
||||
InputType="InputType.Password"
|
||||
Validation="@this.providerValidation.ValidatingAPIKey"
|
||||
/>
|
||||
|
||||
<MudTextField
|
||||
T="string"
|
||||
@bind-Text="@this.DataHostname"
|
||||
Label="Hostname"
|
||||
Disabled="@(!this.DataLLMProvider.IsHostnameNeeded())"
|
||||
Class="mb-3"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Dns"
|
||||
AdornmentColor="Color.Info"
|
||||
Validation="@this.providerValidation.ValidatingHostname"
|
||||
UserAttributes="@SPELLCHECK_ATTRIBUTES"
|
||||
/>
|
||||
|
||||
<MudSelect Disabled="@(!this.DataLLMProvider.IsHostNeeded())" @bind-Value="@this.DataHost" Label="Host" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingHost">
|
||||
@foreach (Host host in Enum.GetValues(typeof(Host)))
|
||||
{
|
||||
if (host.AreEmbeddingsSupported())
|
||||
{
|
||||
<MudSelectItem Value="@host">@host.Name()</MudSelectItem>
|
||||
}
|
||||
}
|
||||
</MudSelect>
|
||||
|
||||
<MudStack Row="@true" AlignItems="AlignItems.Center">
|
||||
@if (this.DataLLMProvider.IsEmbeddingModelProvidedManually(this.DataHost))
|
||||
{
|
||||
<MudTextField
|
||||
T="string"
|
||||
@bind-Text="@this.dataManuallyModel"
|
||||
Label="Model"
|
||||
Class="mb-3"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Dns"
|
||||
AdornmentColor="Color.Info"
|
||||
Validation="@this.ValidateManuallyModel"
|
||||
UserAttributes="@SPELLCHECK_ATTRIBUTES"
|
||||
HelperText="Currently, we cannot query the embedding models of self-hosted systems. Therefore, enter the model name manually."
|
||||
/>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudButton Disabled="@(!this.DataLLMProvider.CanLoadModels(this.DataHost, this.dataAPIKey))" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="this.ReloadModels">Load</MudButton>
|
||||
<MudSelect Disabled="@this.IsNoneProvider" @bind-Value="@this.DataModel" Label="Model" Class="mb-3" OpenIcon="@Icons.Material.Filled.FaceRetouchingNatural" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingModel">
|
||||
@foreach (var model in this.availableModels)
|
||||
{
|
||||
<MudSelectItem Value="@model">@model</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
}
|
||||
</MudStack>
|
||||
|
||||
@* ReSharper disable once CSharpWarnings::CS8974 *@
|
||||
<MudTextField
|
||||
T="string"
|
||||
@bind-Text="@this.DataName"
|
||||
Label="Instance Name"
|
||||
Class="mb-3"
|
||||
MaxLength="40"
|
||||
Counter="40"
|
||||
Immediate="@true"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Lightbulb"
|
||||
AdornmentColor="Color.Info"
|
||||
Validation="@this.providerValidation.ValidatingInstanceName"
|
||||
UserAttributes="@SPELLCHECK_ATTRIBUTES"
|
||||
/>
|
||||
|
||||
</MudForm>
|
||||
<Issues IssuesData="@this.dataIssues"/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="@this.Cancel" Variant="Variant.Filled">Cancel</MudButton>
|
||||
<MudButton OnClick="@this.Store" Variant="Variant.Filled" Color="Color.Primary">
|
||||
@if(this.IsEditing)
|
||||
{
|
||||
@:Update
|
||||
}
|
||||
else
|
||||
{
|
||||
@:Add
|
||||
}
|
||||
</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
@@ -0,0 +1,258 @@
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools.Validation;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
using Host = AIStudio.Provider.SelfHosted.Host;
|
||||
|
||||
namespace AIStudio.Dialogs;
|
||||
|
||||
public partial class EmbeddingDialog : ComponentBase, ISecretId
|
||||
{
|
||||
[CascadingParameter]
|
||||
private MudDialogInstance MudDialog { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The embedding's number in the list.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public uint DataNum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The embedding's ID.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string DataId { get; set; } = Guid.NewGuid().ToString();
|
||||
|
||||
/// <summary>
|
||||
/// The user chosen name.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string DataName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The chosen hostname for self-hosted providers.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string DataHostname { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The host to use, e.g., llama.cpp.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Host DataHost { get; set; } = Host.NONE;
|
||||
|
||||
/// <summary>
|
||||
/// Is this provider self-hosted?
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool IsSelfHosted { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The provider to use.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public LLMProviders DataLLMProvider { get; set; } = LLMProviders.NONE;
|
||||
|
||||
/// <summary>
|
||||
/// The embedding model to use.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Model DataModel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Should the dialog be in editing mode?
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool IsEditing { get; init; }
|
||||
|
||||
[Inject]
|
||||
private SettingsManager SettingsManager { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private ILogger<ProviderDialog> Logger { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private RustService RustService { get; init; } = null!;
|
||||
|
||||
private static readonly Dictionary<string, object?> SPELLCHECK_ATTRIBUTES = new();
|
||||
|
||||
/// <summary>
|
||||
/// The list of used instance names. We need this to check for uniqueness.
|
||||
/// </summary>
|
||||
private List<string> UsedInstanceNames { get; set; } = [];
|
||||
|
||||
private bool dataIsValid;
|
||||
private string[] dataIssues = [];
|
||||
private string dataAPIKey = string.Empty;
|
||||
private string dataManuallyModel = string.Empty;
|
||||
private string dataAPIKeyStorageIssue = string.Empty;
|
||||
private string dataEditingPreviousInstanceName = string.Empty;
|
||||
|
||||
// We get the form reference from Blazor code to validate it manually:
|
||||
private MudForm form = null!;
|
||||
|
||||
private readonly List<Model> availableModels = new();
|
||||
private readonly Encryption encryption = Program.ENCRYPTION;
|
||||
private readonly ProviderValidation providerValidation;
|
||||
|
||||
public EmbeddingDialog()
|
||||
{
|
||||
this.providerValidation = new()
|
||||
{
|
||||
GetProvider = () => this.DataLLMProvider,
|
||||
GetAPIKeyStorageIssue = () => this.dataAPIKeyStorageIssue,
|
||||
GetPreviousInstanceName = () => this.dataEditingPreviousInstanceName,
|
||||
GetUsedInstanceNames = () => this.UsedInstanceNames,
|
||||
GetHost = () => this.DataHost,
|
||||
};
|
||||
}
|
||||
|
||||
private EmbeddingProvider CreateEmbeddingProviderSettings()
|
||||
{
|
||||
var cleanedHostname = this.DataHostname.Trim();
|
||||
return new()
|
||||
{
|
||||
Num = this.DataNum,
|
||||
Id = this.DataId,
|
||||
Name = this.DataName,
|
||||
UsedLLMProvider = this.DataLLMProvider,
|
||||
Model = this.DataLLMProvider is LLMProviders.SELF_HOSTED ? new Model(this.dataManuallyModel, null) : this.DataModel,
|
||||
IsSelfHosted = this.DataLLMProvider is LLMProviders.SELF_HOSTED,
|
||||
Hostname = cleanedHostname.EndsWith('/') ? cleanedHostname[..^1] : cleanedHostname,
|
||||
Host = this.DataHost,
|
||||
};
|
||||
}
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// Configure the spellchecking for the instance name input:
|
||||
this.SettingsManager.InjectSpellchecking(SPELLCHECK_ATTRIBUTES);
|
||||
|
||||
// Load the used instance names:
|
||||
this.UsedInstanceNames = this.SettingsManager.ConfigurationData.EmbeddingProviders.Select(x => x.Name.ToLowerInvariant()).ToList();
|
||||
|
||||
// When editing, we need to load the data:
|
||||
if(this.IsEditing)
|
||||
{
|
||||
this.dataEditingPreviousInstanceName = this.DataName.ToLowerInvariant();
|
||||
|
||||
// When using self-hosted embedding, we must copy the model name:
|
||||
if (this.DataLLMProvider is LLMProviders.SELF_HOSTED)
|
||||
this.dataManuallyModel = this.DataModel.Id;
|
||||
|
||||
//
|
||||
// We cannot load the API key for self-hosted providers:
|
||||
//
|
||||
if (this.DataLLMProvider is LLMProviders.SELF_HOSTED && this.DataHost is not Host.OLLAMA)
|
||||
{
|
||||
await this.ReloadModels();
|
||||
await base.OnInitializedAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
// Load the API key:
|
||||
var requestedSecret = await this.RustService.GetAPIKey(this, isTrying: this.DataLLMProvider is LLMProviders.SELF_HOSTED);
|
||||
if (requestedSecret.Success)
|
||||
this.dataAPIKey = await requestedSecret.Secret.Decrypt(this.encryption);
|
||||
else
|
||||
{
|
||||
this.dataAPIKey = string.Empty;
|
||||
if (this.DataLLMProvider is not LLMProviders.SELF_HOSTED)
|
||||
{
|
||||
this.dataAPIKeyStorageIssue = $"Failed to load the API key from the operating system. The message was: {requestedSecret.Issue}. You might ignore this message and provide the API key again.";
|
||||
await this.form.Validate();
|
||||
}
|
||||
}
|
||||
|
||||
await this.ReloadModels();
|
||||
}
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
// Reset the validation when not editing and on the first render.
|
||||
// We don't want to show validation errors when the user opens the dialog.
|
||||
if(!this.IsEditing && firstRender)
|
||||
this.form.ResetValidation();
|
||||
|
||||
await base.OnAfterRenderAsync(firstRender);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of ISecretId
|
||||
|
||||
public string SecretId => this.DataId;
|
||||
|
||||
public string SecretName => this.DataName;
|
||||
|
||||
#endregion
|
||||
|
||||
private async Task Store()
|
||||
{
|
||||
await this.form.Validate();
|
||||
if (!string.IsNullOrWhiteSpace(this.dataAPIKeyStorageIssue))
|
||||
this.dataAPIKeyStorageIssue = string.Empty;
|
||||
|
||||
// When the data is not valid, we don't store it:
|
||||
if (!this.dataIsValid)
|
||||
return;
|
||||
|
||||
// Use the data model to store the provider.
|
||||
// We just return this data to the parent component:
|
||||
var addedProviderSettings = this.CreateEmbeddingProviderSettings();
|
||||
if (!string.IsNullOrWhiteSpace(this.dataAPIKey))
|
||||
{
|
||||
// Store the API key in the OS secure storage:
|
||||
var storeResponse = await this.RustService.SetAPIKey(this, this.dataAPIKey);
|
||||
if (!storeResponse.Success)
|
||||
{
|
||||
this.dataAPIKeyStorageIssue = $"Failed to store the API key in the operating system. The message was: {storeResponse.Issue}. Please try again.";
|
||||
await this.form.Validate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.MudDialog.Close(DialogResult.Ok(addedProviderSettings));
|
||||
}
|
||||
|
||||
private string? ValidateManuallyModel(string manuallyModel)
|
||||
{
|
||||
if (this.DataLLMProvider is LLMProviders.SELF_HOSTED && string.IsNullOrWhiteSpace(manuallyModel))
|
||||
return "Please enter an embedding model name.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void Cancel() => this.MudDialog.Cancel();
|
||||
|
||||
private async Task ReloadModels()
|
||||
{
|
||||
var currentEmbeddingProviderSettings = this.CreateEmbeddingProviderSettings();
|
||||
var provider = currentEmbeddingProviderSettings.CreateProvider(this.Logger);
|
||||
if(provider is NoProvider)
|
||||
return;
|
||||
|
||||
var models = await provider.GetEmbeddingModels(this.dataAPIKey);
|
||||
|
||||
// Order descending by ID means that the newest models probably come first:
|
||||
var orderedModels = models.OrderByDescending(n => n.Id);
|
||||
|
||||
this.availableModels.Clear();
|
||||
this.availableModels.AddRange(orderedModels);
|
||||
}
|
||||
|
||||
private string APIKeyText => this.DataLLMProvider switch
|
||||
{
|
||||
LLMProviders.SELF_HOSTED => "(Optional) API Key",
|
||||
_ => "API Key",
|
||||
};
|
||||
|
||||
private bool IsNoneProvider => this.DataLLMProvider is LLMProviders.NONE;
|
||||
}
|
||||
@@ -6,13 +6,13 @@
|
||||
<MudForm @ref="@this.form" @bind-IsValid="@this.dataIsValid" @bind-Errors="@this.dataIssues">
|
||||
<MudStack Row="@true" AlignItems="AlignItems.Center">
|
||||
@* ReSharper disable once CSharpWarnings::CS8974 *@
|
||||
<MudSelect @bind-Value="@this.DataLLMProvider" Label="Provider" Class="mb-3" OpenIcon="@Icons.Material.Filled.AccountBalance" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.ValidatingProvider">
|
||||
<MudSelect @bind-Value="@this.DataLLMProvider" Label="Provider" Class="mb-3" OpenIcon="@Icons.Material.Filled.AccountBalance" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingProvider">
|
||||
@foreach (LLMProviders provider in Enum.GetValues(typeof(LLMProviders)))
|
||||
{
|
||||
<MudSelectItem Value="@provider">@provider</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
<MudButton Disabled="@(!this.ShowRegisterButton)" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.GetProviderCreationURL()" Target="_blank">Create account</MudButton>
|
||||
<MudButton Disabled="@(!this.DataLLMProvider.ShowRegisterButton())" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.DataLLMProvider.GetCreationURL()" Target="_blank">Create account</MudButton>
|
||||
</MudStack>
|
||||
|
||||
@* ReSharper disable once CSharpWarnings::CS8974 *@
|
||||
@@ -20,29 +20,29 @@
|
||||
T="string"
|
||||
@bind-Text="@this.dataAPIKey"
|
||||
Label="@this.APIKeyText"
|
||||
Disabled="@(!this.NeedAPIKey)"
|
||||
Disabled="@(!this.DataLLMProvider.IsAPIKeyNeeded(this.DataHost))"
|
||||
Class="mb-3"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.VpnKey"
|
||||
AdornmentColor="Color.Info"
|
||||
InputType="InputType.Password"
|
||||
Validation="@this.ValidatingAPIKey"
|
||||
Validation="@this.providerValidation.ValidatingAPIKey"
|
||||
/>
|
||||
|
||||
<MudTextField
|
||||
T="string"
|
||||
@bind-Text="@this.DataHostname"
|
||||
Label="Hostname"
|
||||
Disabled="@(!this.NeedHostname)"
|
||||
Disabled="@(!this.DataLLMProvider.IsHostnameNeeded())"
|
||||
Class="mb-3"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Dns"
|
||||
AdornmentColor="Color.Info"
|
||||
Validation="@this.ValidatingHostname"
|
||||
Validation="@this.providerValidation.ValidatingHostname"
|
||||
UserAttributes="@SPELLCHECK_ATTRIBUTES"
|
||||
/>
|
||||
|
||||
<MudSelect Disabled="@(!this.NeedHost)" @bind-Value="@this.DataHost" Label="Host" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.ValidatingHost">
|
||||
<MudSelect Disabled="@(!this.DataLLMProvider.IsHostNeeded())" @bind-Value="@this.DataHost" Label="Host" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingHost">
|
||||
@foreach (Host host in Enum.GetValues(typeof(Host)))
|
||||
{
|
||||
<MudSelectItem Value="@host">@host.Name()</MudSelectItem>
|
||||
@@ -50,9 +50,9 @@
|
||||
</MudSelect>
|
||||
|
||||
<MudStack Row="@true" AlignItems="AlignItems.Center">
|
||||
@if (this.ProvideModelManually)
|
||||
@if (this.DataLLMProvider.IsLLMModelProvidedManually())
|
||||
{
|
||||
<MudButton Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.GetModelOverviewURL()" Target="_blank">Show available models</MudButton>
|
||||
<MudButton Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.DataLLMProvider.GetModelsOverviewURL()" Target="_blank">Show available models</MudButton>
|
||||
<MudTextField
|
||||
T="string"
|
||||
@bind-Text="@this.dataManuallyModel"
|
||||
@@ -67,8 +67,8 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudButton Disabled="@(!this.CanLoadModels())" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="this.ReloadModels">Load</MudButton>
|
||||
<MudSelect Disabled="@this.IsNoneProvider" @bind-Value="@this.DataModel" Label="Model" Class="mb-3" OpenIcon="@Icons.Material.Filled.FaceRetouchingNatural" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.ValidatingModel">
|
||||
<MudButton Disabled="@(!this.DataLLMProvider.CanLoadModels(this.DataHost, this.dataAPIKey))" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="this.ReloadModels">Load</MudButton>
|
||||
<MudSelect Disabled="@this.IsNoneProvider" @bind-Value="@this.DataModel" Label="Model" Class="mb-3" OpenIcon="@Icons.Material.Filled.FaceRetouchingNatural" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingModel">
|
||||
@foreach (var model in this.availableModels)
|
||||
{
|
||||
<MudSelectItem Value="@model">@model</MudSelectItem>
|
||||
@@ -89,7 +89,7 @@
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Lightbulb"
|
||||
AdornmentColor="Color.Info"
|
||||
Validation="@this.ValidatingInstanceName"
|
||||
Validation="@this.providerValidation.ValidatingInstanceName"
|
||||
UserAttributes="@SPELLCHECK_ATTRIBUTES"
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools.Validation;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
@@ -11,7 +12,7 @@ namespace AIStudio.Dialogs;
|
||||
/// <summary>
|
||||
/// The provider settings dialog.
|
||||
/// </summary>
|
||||
public partial class ProviderDialog : ComponentBase
|
||||
public partial class ProviderDialog : ComponentBase, ISecretId
|
||||
{
|
||||
[CascadingParameter]
|
||||
private MudDialogInstance MudDialog { get; set; } = null!;
|
||||
@@ -41,7 +42,7 @@ public partial class ProviderDialog : ComponentBase
|
||||
public string DataHostname { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The local host to use, e.g., llama.cpp.
|
||||
/// The host to use, e.g., llama.cpp.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Host DataHost { get; set; } = Host.NONE;
|
||||
@@ -98,18 +99,35 @@ public partial class ProviderDialog : ComponentBase
|
||||
|
||||
private readonly List<Model> availableModels = new();
|
||||
private readonly Encryption encryption = Program.ENCRYPTION;
|
||||
private readonly ProviderValidation providerValidation;
|
||||
|
||||
private Settings.Provider CreateProviderSettings() => new()
|
||||
public ProviderDialog()
|
||||
{
|
||||
Num = this.DataNum,
|
||||
Id = this.DataId,
|
||||
InstanceName = this.DataInstanceName,
|
||||
UsedLLMProvider = this.DataLLMProvider,
|
||||
Model = this.DataLLMProvider is LLMProviders.FIREWORKS ? new Model(this.dataManuallyModel, null) : this.DataModel,
|
||||
IsSelfHosted = this.DataLLMProvider is LLMProviders.SELF_HOSTED,
|
||||
Hostname = this.DataHostname.EndsWith('/') ? this.DataHostname[..^1] : this.DataHostname,
|
||||
Host = this.DataHost,
|
||||
};
|
||||
this.providerValidation = new()
|
||||
{
|
||||
GetProvider = () => this.DataLLMProvider,
|
||||
GetAPIKeyStorageIssue = () => this.dataAPIKeyStorageIssue,
|
||||
GetPreviousInstanceName = () => this.dataEditingPreviousInstanceName,
|
||||
GetUsedInstanceNames = () => this.UsedInstanceNames,
|
||||
GetHost = () => this.DataHost,
|
||||
};
|
||||
}
|
||||
|
||||
private Settings.Provider CreateProviderSettings()
|
||||
{
|
||||
var cleanedHostname = this.DataHostname.Trim();
|
||||
return new()
|
||||
{
|
||||
Num = this.DataNum,
|
||||
Id = this.DataId,
|
||||
InstanceName = this.DataInstanceName,
|
||||
UsedLLMProvider = this.DataLLMProvider,
|
||||
Model = this.DataLLMProvider is LLMProviders.FIREWORKS ? new Model(this.dataManuallyModel, null) : this.DataModel,
|
||||
IsSelfHosted = this.DataLLMProvider is LLMProviders.SELF_HOSTED,
|
||||
Hostname = cleanedHostname.EndsWith('/') ? cleanedHostname[..^1] : cleanedHostname,
|
||||
Host = this.DataHost,
|
||||
};
|
||||
}
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
|
||||
@@ -140,23 +158,10 @@ public partial class ProviderDialog : ComponentBase
|
||||
return;
|
||||
}
|
||||
|
||||
var loadedProviderSettings = this.CreateProviderSettings();
|
||||
var provider = loadedProviderSettings.CreateProvider(this.Logger);
|
||||
if(provider is NoProvider)
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
// Load the API key:
|
||||
var requestedSecret = await this.RustService.GetAPIKey(provider, isTrying: this.DataLLMProvider is LLMProviders.SELF_HOSTED);
|
||||
if(requestedSecret.Success)
|
||||
{
|
||||
var requestedSecret = await this.RustService.GetAPIKey(this, isTrying: this.DataLLMProvider is LLMProviders.SELF_HOSTED);
|
||||
if (requestedSecret.Success)
|
||||
this.dataAPIKey = await requestedSecret.Secret.Decrypt(this.encryption);
|
||||
|
||||
// Now, we try to load the list of available models:
|
||||
await this.ReloadModels();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.dataAPIKey = string.Empty;
|
||||
@@ -165,10 +170,9 @@ public partial class ProviderDialog : ComponentBase
|
||||
this.dataAPIKeyStorageIssue = $"Failed to load the API key from the operating system. The message was: {requestedSecret.Issue}. You might ignore this message and provide the API key again.";
|
||||
await this.form.Validate();
|
||||
}
|
||||
|
||||
// We still try to load the models. Some local hosts don't need an API key:
|
||||
await this.ReloadModels();
|
||||
}
|
||||
|
||||
await this.ReloadModels();
|
||||
}
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
@@ -185,7 +189,15 @@ public partial class ProviderDialog : ComponentBase
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of ISecretId
|
||||
|
||||
public string SecretId => this.DataLLMProvider.ToName();
|
||||
|
||||
public string SecretName => this.DataInstanceName;
|
||||
|
||||
#endregion
|
||||
|
||||
private async Task Store()
|
||||
{
|
||||
await this.form.Validate();
|
||||
@@ -201,11 +213,8 @@ public partial class ProviderDialog : ComponentBase
|
||||
var addedProviderSettings = this.CreateProviderSettings();
|
||||
if (!string.IsNullOrWhiteSpace(this.dataAPIKey))
|
||||
{
|
||||
// We need to instantiate the provider to store the API key:
|
||||
var provider = addedProviderSettings.CreateProvider(this.Logger);
|
||||
|
||||
// Store the API key in the OS secure storage:
|
||||
var storeResponse = await this.RustService.SetAPIKey(provider, this.dataAPIKey);
|
||||
var storeResponse = await this.RustService.SetAPIKey(this, this.dataAPIKey);
|
||||
if (!storeResponse.Success)
|
||||
{
|
||||
this.dataAPIKeyStorageIssue = $"Failed to store the API key in the operating system. The message was: {storeResponse.Issue}. Please try again.";
|
||||
@@ -217,25 +226,6 @@ public partial class ProviderDialog : ComponentBase
|
||||
this.MudDialog.Close(DialogResult.Ok(addedProviderSettings));
|
||||
}
|
||||
|
||||
private string? ValidatingProvider(LLMProviders llmProvider)
|
||||
{
|
||||
if (llmProvider == LLMProviders.NONE)
|
||||
return "Please select a provider.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? ValidatingHost(Host host)
|
||||
{
|
||||
if(this.DataLLMProvider is not LLMProviders.SELF_HOSTED)
|
||||
return null;
|
||||
|
||||
if (host == Host.NONE)
|
||||
return "Please select a host.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? ValidateManuallyModel(string manuallyModel)
|
||||
{
|
||||
if (this.DataLLMProvider is LLMProviders.FIREWORKS && string.IsNullOrWhiteSpace(manuallyModel))
|
||||
@@ -243,64 +233,6 @@ public partial class ProviderDialog : ComponentBase
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? ValidatingModel(Model model)
|
||||
{
|
||||
if(this.DataLLMProvider is LLMProviders.SELF_HOSTED && this.DataHost == Host.LLAMACPP)
|
||||
return null;
|
||||
|
||||
if (model == default)
|
||||
return "Please select a model.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? ValidatingInstanceName(string instanceName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(instanceName))
|
||||
return "Please enter an instance name.";
|
||||
|
||||
if (instanceName.Length > 40)
|
||||
return "The instance name must not exceed 40 characters.";
|
||||
|
||||
// The instance name must be unique:
|
||||
var lowerInstanceName = instanceName.ToLowerInvariant();
|
||||
if (lowerInstanceName != this.dataEditingPreviousInstanceName && this.UsedInstanceNames.Contains(lowerInstanceName))
|
||||
return "The instance name must be unique; the chosen name is already in use.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? ValidatingAPIKey(string apiKey)
|
||||
{
|
||||
if(this.DataLLMProvider is LLMProviders.SELF_HOSTED)
|
||||
return null;
|
||||
|
||||
if(!string.IsNullOrWhiteSpace(this.dataAPIKeyStorageIssue))
|
||||
return this.dataAPIKeyStorageIssue;
|
||||
|
||||
if(string.IsNullOrWhiteSpace(apiKey))
|
||||
return "Please enter an API key.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? ValidatingHostname(string hostname)
|
||||
{
|
||||
if(this.DataLLMProvider != LLMProviders.SELF_HOSTED)
|
||||
return null;
|
||||
|
||||
if(string.IsNullOrWhiteSpace(hostname))
|
||||
return "Please enter a hostname, e.g., http://localhost:1234";
|
||||
|
||||
if(!hostname.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase) && !hostname.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase))
|
||||
return "The hostname must start with either http:// or https://";
|
||||
|
||||
if(!Uri.TryCreate(hostname, UriKind.Absolute, out _))
|
||||
return "The hostname is not a valid HTTP(S) URL.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void Cancel() => this.MudDialog.Cancel();
|
||||
|
||||
@@ -320,109 +252,11 @@ public partial class ProviderDialog : ComponentBase
|
||||
this.availableModels.AddRange(orderedModels);
|
||||
}
|
||||
|
||||
private bool CanLoadModels()
|
||||
{
|
||||
if (this.DataLLMProvider is LLMProviders.SELF_HOSTED)
|
||||
{
|
||||
switch (this.DataHost)
|
||||
{
|
||||
case Host.NONE:
|
||||
return false;
|
||||
|
||||
case Host.LLAMACPP:
|
||||
return false;
|
||||
|
||||
case Host.LM_STUDIO:
|
||||
return true;
|
||||
|
||||
case Host.OLLAMA:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(this.DataLLMProvider is LLMProviders.NONE)
|
||||
return false;
|
||||
|
||||
if(string.IsNullOrWhiteSpace(this.dataAPIKey))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ShowRegisterButton => this.DataLLMProvider switch
|
||||
{
|
||||
LLMProviders.OPEN_AI => true,
|
||||
LLMProviders.MISTRAL => true,
|
||||
LLMProviders.ANTHROPIC => true,
|
||||
LLMProviders.GOOGLE => true,
|
||||
|
||||
LLMProviders.GROQ => true,
|
||||
LLMProviders.FIREWORKS => true,
|
||||
|
||||
_ => false,
|
||||
};
|
||||
|
||||
private bool NeedAPIKey => this.DataLLMProvider switch
|
||||
{
|
||||
LLMProviders.OPEN_AI => true,
|
||||
LLMProviders.MISTRAL => true,
|
||||
LLMProviders.ANTHROPIC => true,
|
||||
LLMProviders.GOOGLE => true,
|
||||
|
||||
LLMProviders.GROQ => true,
|
||||
LLMProviders.FIREWORKS => true,
|
||||
|
||||
LLMProviders.SELF_HOSTED => this.DataHost is Host.OLLAMA,
|
||||
|
||||
_ => false,
|
||||
};
|
||||
|
||||
private string APIKeyText => this.DataLLMProvider switch
|
||||
{
|
||||
LLMProviders.SELF_HOSTED => "(Optional) API Key",
|
||||
_ => "API Key",
|
||||
};
|
||||
|
||||
private bool NeedHostname => this.DataLLMProvider switch
|
||||
{
|
||||
LLMProviders.SELF_HOSTED => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
private bool NeedHost => this.DataLLMProvider switch
|
||||
{
|
||||
LLMProviders.SELF_HOSTED => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
private bool ProvideModelManually => this.DataLLMProvider switch
|
||||
{
|
||||
LLMProviders.FIREWORKS => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
private string GetModelOverviewURL() => this.DataLLMProvider switch
|
||||
{
|
||||
LLMProviders.FIREWORKS => "https://fireworks.ai/models?show=Serverless",
|
||||
|
||||
_ => string.Empty,
|
||||
};
|
||||
|
||||
private string GetProviderCreationURL() => this.DataLLMProvider switch
|
||||
{
|
||||
LLMProviders.OPEN_AI => "https://platform.openai.com/signup",
|
||||
LLMProviders.MISTRAL => "https://console.mistral.ai/",
|
||||
LLMProviders.ANTHROPIC => "https://console.anthropic.com/dashboard",
|
||||
LLMProviders.GOOGLE => "https://console.cloud.google.com/",
|
||||
|
||||
LLMProviders.GROQ => "https://console.groq.com/",
|
||||
LLMProviders.FIREWORKS => "https://fireworks.ai/login",
|
||||
|
||||
_ => string.Empty,
|
||||
};
|
||||
|
||||
private bool IsNoneProvider => this.DataLLMProvider is LLMProviders.NONE;
|
||||
}
|
||||
@@ -4,4 +4,5 @@ global using AIStudio.Tools;
|
||||
|
||||
global using Microsoft.JSInterop;
|
||||
|
||||
global using MudBlazor;
|
||||
global using MudBlazor;
|
||||
global using MudExtensions;
|
||||
@@ -96,21 +96,42 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDis
|
||||
|
||||
await this.themeProvider.WatchSystemPreference(this.SystemeThemeChanged);
|
||||
await this.UpdateThemeConfiguration();
|
||||
|
||||
var palette = this.ColorTheme.GetCurrentPalette(this.SettingsManager);
|
||||
this.navItems = new List<NavBarItem>
|
||||
{
|
||||
new("Home", Icons.Material.Filled.Home, palette.DarkLighten, palette.GrayLight, Routes.HOME, true),
|
||||
new("Chat", Icons.Material.Filled.Chat, palette.DarkLighten, palette.GrayLight, Routes.CHAT, false),
|
||||
new("Assistants", Icons.Material.Filled.Apps, palette.DarkLighten, palette.GrayLight, Routes.ASSISTANTS, false),
|
||||
new("Supporters", Icons.Material.Filled.Favorite, palette.Error.Value, "#801a00", Routes.SUPPORTERS, false),
|
||||
new("About", Icons.Material.Filled.Info, palette.DarkLighten, palette.GrayLight, Routes.ABOUT, false),
|
||||
new("Settings", Icons.Material.Filled.Settings, palette.DarkLighten, palette.GrayLight, Routes.SETTINGS, false),
|
||||
};
|
||||
|
||||
this.LoadNavItems();
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
private void LoadNavItems()
|
||||
{
|
||||
var palette = this.ColorTheme.GetCurrentPalette(this.SettingsManager);
|
||||
var isWriterModePreviewEnabled = PreviewFeatures.PRE_WRITER_MODE_2024.IsEnabled(this.SettingsManager);
|
||||
if (!isWriterModePreviewEnabled)
|
||||
{
|
||||
this.navItems = new List<NavBarItem>
|
||||
{
|
||||
new("Home", Icons.Material.Filled.Home, palette.DarkLighten, palette.GrayLight, Routes.HOME, true),
|
||||
new("Chat", Icons.Material.Filled.Chat, palette.DarkLighten, palette.GrayLight, Routes.CHAT, false),
|
||||
new("Assistants", Icons.Material.Filled.Apps, palette.DarkLighten, palette.GrayLight, Routes.ASSISTANTS, false),
|
||||
new("Supporters", Icons.Material.Filled.Favorite, palette.Error.Value, "#801a00", Routes.SUPPORTERS, false),
|
||||
new("About", Icons.Material.Filled.Info, palette.DarkLighten, palette.GrayLight, Routes.ABOUT, false),
|
||||
new("Settings", Icons.Material.Filled.Settings, palette.DarkLighten, palette.GrayLight, Routes.SETTINGS, false),
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
this.navItems = new List<NavBarItem>
|
||||
{
|
||||
new("Home", Icons.Material.Filled.Home, palette.DarkLighten, palette.GrayLight, Routes.HOME, true),
|
||||
new("Chat", Icons.Material.Filled.Chat, palette.DarkLighten, palette.GrayLight, Routes.CHAT, false),
|
||||
new("Assistants", Icons.Material.Filled.Apps, palette.DarkLighten, palette.GrayLight, Routes.ASSISTANTS, false),
|
||||
new("Writer", Icons.Material.Filled.Create, palette.DarkLighten, palette.GrayLight, Routes.WRITER, false),
|
||||
new("Supporters", Icons.Material.Filled.Favorite, palette.Error.Value, "#801a00", Routes.SUPPORTERS, false),
|
||||
new("About", Icons.Material.Filled.Info, palette.DarkLighten, palette.GrayLight, Routes.ABOUT, false),
|
||||
new("Settings", Icons.Material.Filled.Settings, palette.DarkLighten, palette.GrayLight, Routes.SETTINGS, false),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of IMessageBusReceiver
|
||||
@@ -143,6 +164,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDis
|
||||
this.navBarOpen = false;
|
||||
|
||||
await this.UpdateThemeConfiguration();
|
||||
this.LoadNavItems();
|
||||
this.StateHasChanged();
|
||||
break;
|
||||
|
||||
|
||||
@@ -45,9 +45,10 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.70" />
|
||||
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="8.0.10" />
|
||||
<PackageReference Include="MudBlazor" Version="7.14.0" />
|
||||
<PackageReference Include="CodeBeam.MudBlazor.Extensions" Version="7.1.0" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.71" />
|
||||
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="9.0.0" />
|
||||
<PackageReference Include="MudBlazor" Version="7.15.0" />
|
||||
<PackageReference Include="MudBlazor.Markdown" Version="7.14.0" />
|
||||
<PackageReference Include="ReverseMarkdown" Version="4.6.0" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
<ThirdPartyComponent Name=".NET" Developer="Microsoft & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/dotnet/runtime/blob/main/LICENSE.TXT" RepositoryUrl="https://github.com/dotnet" UseCase="The C# language is used for the implementation of the user interface and the backend. To implement the user interface with C#, the Blazor technology from ASP.NET Core is used. All these technologies are integrated into the .NET SDK."/>
|
||||
<ThirdPartyComponent Name="MudBlazor" Developer="Jonny Larsson, Meinrad Recheis & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/MudBlazor/MudBlazor/blob/dev/LICENSE" RepositoryUrl="https://github.com/MudBlazor/MudBlazor/" UseCase="Building on .NET, ASP.NET Core, and Blazor, MudBlazor is used as a library for designing and developing the user interface. It is a great project that significantly accelerates the development of advanced user interfaces with Blazor."/>
|
||||
<ThirdPartyComponent Name="MudBlazor.Markdown" Developer="My Nihongo & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/MyNihongo/MudBlazor.Markdown/blob/main/LICENSE" RepositoryUrl="https://github.com/MyNihongo/MudBlazor.Markdown" UseCase="This component is used to render Markdown text. This is important because the LLM often responds with Markdown-formatted text, allowing us to present it in a way that is easier to read."/>
|
||||
<ThirdPartyComponent Name="CodeBeam.MudBlazor.Extensions" Developer="Mehmet Can Karagöz & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/CodeBeamOrg/CodeBeam.MudBlazor.Extensions/blob/dev/LICENSE" RepositoryUrl="https://github.com/CodeBeamOrg/CodeBeam.MudBlazor.Extensions" UseCase="This library is used to extend the MudBlazor library. It provides additional components that are not part of the MudBlazor library."/>
|
||||
<ThirdPartyComponent Name="Rust" Developer="Graydon Hoare, Rust Foundation, Rust developers & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/rust-lang/rust/blob/master/LICENSE-MIT" RepositoryUrl="https://github.com/rust-lang/rust" UseCase="The .NET backend cannot be started as a desktop app. Therefore, I use a second backend in Rust, which I call runtime. With Rust as the runtime, Tauri can be used to realize a typical desktop app. Thanks to Rust, this app can be offered for Windows, macOS, and Linux desktops. Rust is a great language for developing safe and high-performance software."/>
|
||||
<ThirdPartyComponent Name="Tauri" Developer="Daniel Thompson-Yvetot, Lucas Nogueira, Tensor, Boscop, Serge Zaitsev, George Burton & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/tauri-apps/tauri/blob/dev/LICENSE_MIT" RepositoryUrl="https://github.com/tauri-apps/tauri" UseCase="Tauri is used to host the Blazor user interface. It is a great project that allows the creation of desktop applications using web technologies. I love Tauri!"/>
|
||||
<ThirdPartyComponent Name="Rocket" Developer="Sergio Benitez & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/rwf2/Rocket/blob/master/LICENSE-MIT" RepositoryUrl="https://github.com/rwf2/Rocket" UseCase="We use Rocket to implement the runtime API. This is necessary because the runtime must be able to communicate with the user interface (IPC). Rocket is a great framework for implementing web APIs in Rust."/>
|
||||
|
||||
@@ -17,9 +17,6 @@ namespace AIStudio.Pages;
|
||||
/// </summary>
|
||||
public partial class Chat : MSGComponentBase, IAsyncDisposable
|
||||
{
|
||||
[Inject]
|
||||
private SettingsManager SettingsManager { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private ThreadSafeRandom RNG { get; init; } = null!;
|
||||
|
||||
@@ -64,7 +61,6 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable
|
||||
// Configure the spellchecking for the user input:
|
||||
this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES);
|
||||
|
||||
this.providerSettings = this.SettingsManager.GetPreselectedProvider(Tools.Components.CHAT);
|
||||
this.currentProfile = this.SettingsManager.GetPreselectedProfile(Tools.Components.CHAT);
|
||||
var deferredContent = MessageBus.INSTANCE.CheckDeferredMessages<ChatThread>(Event.SEND_TO_CHAT).FirstOrDefault();
|
||||
if (deferredContent is not null)
|
||||
@@ -84,8 +80,8 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if(this.chatThread.WorkspaceId != Guid.Empty)
|
||||
|
||||
if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY)
|
||||
{
|
||||
this.autoSaveEnabled = true;
|
||||
this.mustStoreChat = true;
|
||||
@@ -106,24 +102,38 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable
|
||||
this.loadChat = deferredLoading;
|
||||
this.mustLoadChat = true;
|
||||
}
|
||||
|
||||
|
||||
this.SelectProviderWhenLoadingChat();
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender && this.workspaces is not null && this.chatThread is not null && this.mustStoreChat)
|
||||
if (firstRender && this.chatThread is not null && this.mustStoreChat)
|
||||
{
|
||||
this.mustStoreChat = false;
|
||||
await this.workspaces.StoreChat(this.chatThread, false);
|
||||
|
||||
if(this.workspaces is not null)
|
||||
await this.workspaces.StoreChat(this.chatThread);
|
||||
else
|
||||
await WorkspaceBehaviour.StoreChat(this.chatThread);
|
||||
|
||||
this.currentWorkspaceId = this.chatThread.WorkspaceId;
|
||||
this.currentWorkspaceName = await this.workspaces.LoadWorkspaceName(this.chatThread.WorkspaceId);
|
||||
this.currentWorkspaceName = await WorkspaceBehaviour.LoadWorkspaceName(this.chatThread.WorkspaceId);
|
||||
}
|
||||
|
||||
if (firstRender && this.workspaces is not null && this.mustLoadChat)
|
||||
if (firstRender && this.mustLoadChat)
|
||||
{
|
||||
this.mustLoadChat = false;
|
||||
await this.workspaces.LoadChat(this.loadChat);
|
||||
this.chatThread = await WorkspaceBehaviour.LoadChat(this.loadChat);
|
||||
|
||||
if(this.chatThread is not null)
|
||||
{
|
||||
this.currentWorkspaceName = await WorkspaceBehaviour.LoadWorkspaceName(this.chatThread.WorkspaceId);
|
||||
this.SelectProviderWhenLoadingChat();
|
||||
}
|
||||
|
||||
this.StateHasChanged();
|
||||
}
|
||||
|
||||
if(this.mustScrollToBottomAfterRender)
|
||||
@@ -192,6 +202,7 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable
|
||||
{
|
||||
this.chatThread = new()
|
||||
{
|
||||
SelectedProvider = this.providerSettings.Id,
|
||||
WorkspaceId = this.currentWorkspaceId,
|
||||
ChatId = Guid.NewGuid(),
|
||||
Name = threadName,
|
||||
@@ -323,16 +334,22 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable
|
||||
|
||||
private async Task SaveThread()
|
||||
{
|
||||
if(this.workspaces is null)
|
||||
return;
|
||||
|
||||
if(this.chatThread is null)
|
||||
return;
|
||||
|
||||
if (!this.CanThreadBeSaved)
|
||||
return;
|
||||
|
||||
//
|
||||
// When the workspace component is visible, we store the chat
|
||||
// through the workspace component. The advantage of this is that
|
||||
// the workspace gets updated automatically when the chat is saved.
|
||||
//
|
||||
if (this.workspaces is not null)
|
||||
await this.workspaces.StoreChat(this.chatThread);
|
||||
else
|
||||
await WorkspaceBehaviour.StoreChat(this.chatThread);
|
||||
|
||||
await this.workspaces.StoreChat(this.chatThread);
|
||||
this.hasUnsavedChanges = false;
|
||||
}
|
||||
|
||||
@@ -364,24 +381,36 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.chatThread is not null && this.workspaces is not null && deletePreviousChat)
|
||||
if (this.chatThread is not null && deletePreviousChat)
|
||||
{
|
||||
string chatPath;
|
||||
if (this.chatThread.WorkspaceId == Guid.Empty)
|
||||
{
|
||||
chatPath = Path.Join(SettingsManager.DataDirectory, "tempChats", this.chatThread.ChatId.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
chatPath = Path.Join(SettingsManager.DataDirectory, "workspaces", this.chatThread.WorkspaceId.ToString(), this.chatThread.ChatId.ToString());
|
||||
}
|
||||
|
||||
await this.workspaces.DeleteChat(chatPath, askForConfirmation: false, unloadChat: true);
|
||||
|
||||
if(this.workspaces is null)
|
||||
await WorkspaceBehaviour.DeleteChat(this.DialogService, this.chatThread.WorkspaceId, this.chatThread.ChatId, askForConfirmation: false);
|
||||
else
|
||||
await this.workspaces.DeleteChat(chatPath, askForConfirmation: false, unloadChat: true);
|
||||
}
|
||||
|
||||
this.isStreaming = false;
|
||||
this.hasUnsavedChanges = false;
|
||||
this.userInput = string.Empty;
|
||||
|
||||
switch (this.SettingsManager.ConfigurationData.Chat.AddChatProviderBehavior)
|
||||
{
|
||||
case AddChatProviderBehavior.ADDED_CHATS_USE_DEFAULT_PROVIDER:
|
||||
this.providerSettings = this.SettingsManager.GetPreselectedProvider(Tools.Components.CHAT);
|
||||
break;
|
||||
|
||||
default:
|
||||
case AddChatProviderBehavior.ADDED_CHATS_USE_LATEST_PROVIDER:
|
||||
if(this.providerSettings == default)
|
||||
this.providerSettings = this.SettingsManager.GetPreselectedProvider(Tools.Components.CHAT);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!useSameWorkspace)
|
||||
{
|
||||
@@ -393,6 +422,7 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable
|
||||
{
|
||||
this.chatThread = new()
|
||||
{
|
||||
SelectedProvider = this.providerSettings.Id,
|
||||
WorkspaceId = this.currentWorkspaceId,
|
||||
ChatId = Guid.NewGuid(),
|
||||
Name = string.Empty,
|
||||
@@ -414,9 +444,6 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable
|
||||
if(this.chatThread is null)
|
||||
return;
|
||||
|
||||
if(this.workspaces is null)
|
||||
return;
|
||||
|
||||
if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_MANUALLY && this.hasUnsavedChanges)
|
||||
{
|
||||
var confirmationDialogParameters = new DialogParameters
|
||||
@@ -447,26 +474,21 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable
|
||||
return;
|
||||
|
||||
// Delete the chat from the current workspace or the temporary storage:
|
||||
if (this.chatThread!.WorkspaceId == Guid.Empty)
|
||||
{
|
||||
// Case: The chat is stored in the temporary storage:
|
||||
await this.workspaces.DeleteChat(Path.Join(SettingsManager.DataDirectory, "tempChats", this.chatThread.ChatId.ToString()), askForConfirmation: false, unloadChat: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Case: The chat is stored in a workspace.
|
||||
await this.workspaces.DeleteChat(Path.Join(SettingsManager.DataDirectory, "workspaces", this.chatThread.WorkspaceId.ToString(), this.chatThread.ChatId.ToString()), askForConfirmation: false, unloadChat: false);
|
||||
}
|
||||
await WorkspaceBehaviour.DeleteChat(this.DialogService, this.chatThread!.WorkspaceId, this.chatThread.ChatId, askForConfirmation: false);
|
||||
|
||||
this.chatThread!.WorkspaceId = workspaceId;
|
||||
await this.SaveThread();
|
||||
|
||||
this.currentWorkspaceId = this.chatThread.WorkspaceId;
|
||||
this.currentWorkspaceName = await this.workspaces.LoadWorkspaceName(this.chatThread.WorkspaceId);
|
||||
this.currentWorkspaceName = await WorkspaceBehaviour.LoadWorkspaceName(this.chatThread.WorkspaceId);
|
||||
}
|
||||
|
||||
private async Task LoadedChatChanged()
|
||||
{
|
||||
//
|
||||
// It should not happen that the workspace component is not loaded
|
||||
// because the workspace component is calling this method.
|
||||
//
|
||||
if(this.workspaces is null)
|
||||
return;
|
||||
|
||||
@@ -474,8 +496,10 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable
|
||||
this.hasUnsavedChanges = false;
|
||||
this.userInput = string.Empty;
|
||||
this.currentWorkspaceId = this.chatThread?.WorkspaceId ?? Guid.Empty;
|
||||
this.currentWorkspaceName = this.chatThread is null ? string.Empty : await this.workspaces.LoadWorkspaceName(this.chatThread.WorkspaceId);
|
||||
|
||||
this.currentWorkspaceName = this.chatThread is null ? string.Empty : await WorkspaceBehaviour.LoadWorkspaceName(this.chatThread.WorkspaceId);
|
||||
|
||||
this.SelectProviderWhenLoadingChat();
|
||||
|
||||
this.userInput = string.Empty;
|
||||
if (this.SettingsManager.ConfigurationData.Chat.ShowLatestMessageAfterLoading)
|
||||
{
|
||||
@@ -495,6 +519,27 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable
|
||||
this.chatThread = null;
|
||||
}
|
||||
|
||||
private void SelectProviderWhenLoadingChat()
|
||||
{
|
||||
var chatProvider = this.chatThread?.SelectedProvider;
|
||||
switch (this.SettingsManager.ConfigurationData.Chat.LoadingProviderBehavior)
|
||||
{
|
||||
default:
|
||||
case LoadingChatProviderBehavior.USE_CHAT_PROVIDER_IF_AVAILABLE:
|
||||
this.providerSettings = this.SettingsManager.GetPreselectedProvider(Tools.Components.CHAT, chatProvider);
|
||||
break;
|
||||
|
||||
case LoadingChatProviderBehavior.ALWAYS_USE_DEFAULT_CHAT_PROVIDER:
|
||||
this.providerSettings = this.SettingsManager.GetPreselectedProvider(Tools.Components.CHAT);
|
||||
break;
|
||||
|
||||
case LoadingChatProviderBehavior.ALWAYS_USE_LATEST_CHAT_PROVIDER:
|
||||
if(this.providerSettings == default)
|
||||
this.providerSettings = this.SettingsManager.GetPreselectedProvider(Tools.Components.CHAT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#region Overrides of MSGComponentBase
|
||||
|
||||
public override async Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
|
||||
|
||||
@@ -32,13 +32,13 @@ 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, ollama, LM Studio, or Fireworks. Support for Google Gemini and Replicate 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."),
|
||||
new TextItem("Flexibility", "Choose the provider and model best suited for your current task."),
|
||||
new TextItem("No bloatware", "The app requires minimal storage for installation and operates with low memory usage. Additionally, it has a minimal impact on system resources, which is beneficial for battery life."),
|
||||
new("Free of charge", "The app is free to use, both for personal and commercial purposes."),
|
||||
new("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), Google Gemini, and self-hosted models using llama.cpp, ollama, LM Studio, Groq, or Fireworks."),
|
||||
new("Unrestricted usage", "Unlike services like ChatGPT, which impose limits after intensive use, MindWork AI Studio offers unlimited usage through the providers API."),
|
||||
new("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("Privacy", "The data entered into the app is not used for training by the providers since we are using the provider's API."),
|
||||
new("Flexibility", "Choose the provider and model best suited for your current task."),
|
||||
new("No bloatware", "The app requires minimal storage for installation and operates with low memory usage. Additionally, it has a minimal impact on system resources, which is beneficial for battery life."),
|
||||
];
|
||||
|
||||
private const string QUICK_START_GUIDE =
|
||||
|
||||
@@ -40,11 +40,11 @@
|
||||
<MudTd>
|
||||
@if (context.UsedLLMProvider is not LLMProviders.SELF_HOSTED)
|
||||
{
|
||||
@this.GetProviderModelName(context)
|
||||
@this.GetLLMProviderModelName(context)
|
||||
}
|
||||
else if (context.UsedLLMProvider is LLMProviders.SELF_HOSTED && context.Host is not Host.LLAMACPP)
|
||||
{
|
||||
@this.GetProviderModelName(context)
|
||||
@this.GetLLMProviderModelName(context)
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -52,13 +52,13 @@
|
||||
}
|
||||
</MudTd>
|
||||
<MudTd Style="text-align: left;">
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.OpenInBrowser" Class="ma-2" Href="@this.GetProviderDashboardURL(context.UsedLLMProvider)" Target="_blank" Disabled="@(!this.HasDashboard(context.UsedLLMProvider))">
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.OpenInBrowser" Class="ma-2" Href="@context.UsedLLMProvider.GetDashboardURL()" Target="_blank" Disabled="@(!context.UsedLLMProvider.HasDashboard())">
|
||||
Open Dashboard
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Edit" Class="ma-2" OnClick="() => this.EditProvider(context)">
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Edit" Class="ma-2" OnClick="() => this.EditLLMProvider(context)">
|
||||
Edit
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Error" StartIcon="@Icons.Material.Filled.Delete" Class="ma-2" OnClick="() => this.DeleteProvider(context)">
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Error" StartIcon="@Icons.Material.Filled.Delete" Class="ma-2" OnClick="() => this.DeleteLLMProvider(context)">
|
||||
Delete
|
||||
</MudButton>
|
||||
</MudTd>
|
||||
@@ -70,7 +70,7 @@
|
||||
<MudText Typo="Typo.h6" Class="mt-3">No providers configured yet.</MudText>
|
||||
}
|
||||
|
||||
<MudButton Variant="Variant.Filled" Color="@Color.Primary" StartIcon="@Icons.Material.Filled.AddRoad" Class="mt-3 mb-6" OnClick="@this.AddProvider">
|
||||
<MudButton Variant="Variant.Filled" Color="@Color.Primary" StartIcon="@Icons.Material.Filled.AddRoad" Class="mt-3 mb-6" OnClick="@this.AddLLMProvider">
|
||||
Add Provider
|
||||
</MudButton>
|
||||
|
||||
@@ -131,6 +131,73 @@
|
||||
|
||||
</ExpansionPanel>
|
||||
|
||||
@if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager))
|
||||
{
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.IntegrationInstructions" HeaderText="Configure Embeddings">
|
||||
<PreviewPrototype/>
|
||||
<MudText Typo="Typo.h4" Class="mb-3">
|
||||
Configured Embeddings
|
||||
</MudText>
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
|
||||
Embeddings are a way to represent words, sentences, entire documents, or even images and videos as digital
|
||||
fingerprints. Just like each person has a unique fingerprint, embedding models create unique digital patterns
|
||||
that capture the meaning and characteristics of the content they analyze. When two things are similar in meaning
|
||||
or content, their digital fingerprints will look very similar. For example, the fingerprints for 'happy' and
|
||||
'joyful' would be more alike than those for 'happy' and 'sad'.
|
||||
</MudJustifiedText>
|
||||
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
|
||||
This helps AI Studio understand and compare things in a way that's similar to how humans do. When you're working on
|
||||
something, AI Studio can automatically identify related documents and data by comparing their digital fingerprints.
|
||||
For instance, if you're writing about customer service, AI Studio can instantly find other documents in your data that
|
||||
discuss similar topics or experiences, even if they use different words.
|
||||
</MudJustifiedText>
|
||||
<MudTable Items="@this.SettingsManager.ConfigurationData.EmbeddingProviders" Hover="@true" Class="border-dashed border rounded-lg">
|
||||
<ColGroup>
|
||||
<col style="width: 3em;"/>
|
||||
<col style="width: 12em;"/>
|
||||
<col style="width: 12em;"/>
|
||||
<col/>
|
||||
<col style="width: 40em;"/>
|
||||
</ColGroup>
|
||||
<HeaderContent>
|
||||
<MudTh>#</MudTh>
|
||||
<MudTh>Name</MudTh>
|
||||
<MudTh>Provider</MudTh>
|
||||
<MudTh>Model</MudTh>
|
||||
<MudTh Style="text-align: left;">Actions</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd>@context.Num</MudTd>
|
||||
<MudTd>@context.Name</MudTd>
|
||||
<MudTd>@context.UsedLLMProvider</MudTd>
|
||||
<MudTd>@this.GetEmbeddingProviderModelName(context)</MudTd>
|
||||
|
||||
<MudTd Style="text-align: left;">
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.OpenInBrowser" Class="ma-2" Href="@context.UsedLLMProvider.GetDashboardURL()" Target="_blank" Disabled="@(!context.UsedLLMProvider.HasDashboard())">
|
||||
Open Dashboard
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Edit" Class="ma-2" OnClick="() => this.EditEmbeddingProvider(context)">
|
||||
Edit
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Error" StartIcon="@Icons.Material.Filled.Delete" Class="ma-2" OnClick="() => this.DeleteEmbeddingProvider(context)">
|
||||
Delete
|
||||
</MudButton>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
|
||||
@if (this.SettingsManager.ConfigurationData.EmbeddingProviders.Count == 0)
|
||||
{
|
||||
<MudText Typo="Typo.h6" Class="mt-3">No embeddings configured yet.</MudText>
|
||||
}
|
||||
|
||||
<MudButton Variant="Variant.Filled" Color="@Color.Primary" StartIcon="@Icons.Material.Filled.AddRoad" Class="mt-3 mb-6" OnClick="@this.AddEmbeddingProvider">
|
||||
Add Embedding
|
||||
</MudButton>
|
||||
</ExpansionPanel>
|
||||
}
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Person4" HeaderText="Configure Profiles">
|
||||
<MudText Typo="Typo.h4" Class="mb-3">Your Profiles</MudText>
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
|
||||
@@ -186,16 +253,30 @@
|
||||
<ConfigurationOption OptionDescription="Enable spellchecking?" LabelOn="Spellchecking is enabled" LabelOff="Spellchecking is disabled" State="@(() => this.SettingsManager.ConfigurationData.App.EnableSpellchecking)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.App.EnableSpellchecking = updatedState)" OptionHelp="When enabled, spellchecking will be active in all input fields. Depending on your operating system, errors may not be visually highlighted, but right-clicking may still offer possible corrections." />
|
||||
<ConfigurationSelect OptionDescription="Check for updates" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.UpdateBehavior)" Data="@ConfigurationSelectDataFactory.GetUpdateBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.UpdateBehavior = selectedValue)" OptionHelp="How often should we check for app updates?"/>
|
||||
<ConfigurationSelect OptionDescription="Navigation bar behavior" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.NavigationBehavior)" Data="@ConfigurationSelectDataFactory.GetNavBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.NavigationBehavior = selectedValue)" OptionHelp="Select the desired behavior for the navigation bar."/>
|
||||
<ConfigurationProviderSelection Data="@this.availableProviders" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.PreselectedProvider = selectedValue)" HelpText="@(() => "Would you like to set one provider as the default for the entire app? When you configure a different provider for an assistant, it will always take precedence.")"/>
|
||||
<ConfigurationSelect OptionDescription="Preview feature visibility" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.PreviewVisibility)" Data="@ConfigurationSelectDataFactory.GetPreviewVisibility()" SelectionUpdate="@this.UpdatePreviewFeatures" OptionHelp="Do you want to show preview features in the app?"/>
|
||||
|
||||
@if(this.SettingsManager.ConfigurationData.App.PreviewVisibility > PreviewVisibility.NONE)
|
||||
{
|
||||
var availablePreviewFeatures = ConfigurationSelectDataFactory.GetPreviewFeaturesData(this.SettingsManager).ToList();
|
||||
if (availablePreviewFeatures.Count > 0)
|
||||
{
|
||||
<ConfigurationMultiSelect OptionDescription="Select preview features" SelectedValues="@(() => this.SettingsManager.ConfigurationData.App.EnabledPreviewFeatures)" Data="@availablePreviewFeatures" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.EnabledPreviewFeatures = selectedValue)" OptionHelp="Which preview features would you like to enable?"/>
|
||||
}
|
||||
}
|
||||
|
||||
<ConfigurationProviderSelection Data="@this.availableLLMProviders" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.PreselectedProvider = selectedValue)" HelpText="@(() => "Would you like to set one provider as the default for the entire app? When you configure a different provider for an assistant, it will always take precedence.")"/>
|
||||
<ConfigurationSelect OptionDescription="Preselect one of your profiles?" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.PreselectedProfile)" Data="@ConfigurationSelectDataFactory.GetProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.PreselectedProfile = selectedValue)" OptionHelp="Would you like to set one of your profiles as the default for the entire app? When you configure a different profile for an assistant, it will always take precedence."/>
|
||||
</ExpansionPanel>
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Chat" HeaderText="Chat Options">
|
||||
<ConfigurationSelect OptionDescription="Shortcut to send input" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Chat.ShortcutSendBehavior)" Data="@ConfigurationSelectDataFactory.GetSendBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Chat.ShortcutSendBehavior = selectedValue)" OptionHelp="Do you want to use any shortcut to send your input?"/>
|
||||
<ConfigurationOption OptionDescription="Show the latest message after loading?" LabelOn="Latest message is shown, after loading a chat" LabelOff="First (oldest) message is shown, after loading a chat" State="@(() => this.SettingsManager.ConfigurationData.Chat.ShowLatestMessageAfterLoading)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.Chat.ShowLatestMessageAfterLoading = updatedState)" OptionHelp="When enabled, the latest message is shown after loading a chat. When disabled, the first (oldest) message is shown."/>
|
||||
<ConfigurationSelect OptionDescription="Provider selection when creating new chats" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Chat.AddChatProviderBehavior)" Data="@ConfigurationSelectDataFactory.GetAddChatProviderBehavior()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Chat.AddChatProviderBehavior = selectedValue)" OptionHelp="Control how the LLM provider for added chats is selected."/>
|
||||
<ConfigurationSelect OptionDescription="Provider selection when loading a chat and sending assistant results to chat" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Chat.LoadingProviderBehavior)" Data="@ConfigurationSelectDataFactory.GetLoadingChatProviderBehavior()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Chat.LoadingProviderBehavior = selectedValue)" OptionHelp="Control how the LLM provider for loaded chats is selected and when assistant results are sent to chat."/>
|
||||
|
||||
<MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg">
|
||||
<ConfigurationOption OptionDescription="Preselect chat options?" LabelOn="Chat options are preselected" LabelOff="No chat options are preselected" State="@(() => this.SettingsManager.ConfigurationData.Chat.PreselectOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.Chat.PreselectOptions = updatedState)" OptionHelp="When enabled, you can preselect chat options. This is might be useful when you prefer a specific provider."/>
|
||||
<ConfigurationProviderSelection Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.Chat.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Chat.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Chat.PreselectedProvider = selectedValue)"/>
|
||||
<ConfigurationProviderSelection Data="@this.availableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.Chat.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Chat.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Chat.PreselectedProvider = selectedValue)"/>
|
||||
<ConfigurationSelect OptionDescription="Preselect one of your profiles?" Disabled="@(() => !this.SettingsManager.ConfigurationData.Chat.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Chat.PreselectedProfile)" Data="@ConfigurationSelectDataFactory.GetProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Chat.PreselectedProfile = selectedValue)" OptionHelp="Would you like to set one of your profiles as the default for chats?"/>
|
||||
</MudPaper>
|
||||
</ExpansionPanel>
|
||||
@@ -215,7 +296,7 @@
|
||||
<ConfigurationOption OptionDescription="Preselect icon options?" LabelOn="Icon options are preselected" LabelOff="No icon options are preselected" State="@(() => this.SettingsManager.ConfigurationData.IconFinder.PreselectOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.IconFinder.PreselectOptions = updatedState)" OptionHelp="When enabled, you can preselect the icon options. This is might be useful when you prefer a specific icon source or LLM model."/>
|
||||
<ConfigurationSelect OptionDescription="Preselect the icon source" Disabled="@(() => !this.SettingsManager.ConfigurationData.IconFinder.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.IconFinder.PreselectedSource)" Data="@ConfigurationSelectDataFactory.GetIconSourcesData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.IconFinder.PreselectedSource = selectedValue)" OptionHelp="Which icon source should be preselected?"/>
|
||||
<ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.IconFinder.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.IconFinder.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.IconFinder.MinimumProviderConfidence = selectedValue)"/>
|
||||
<ConfigurationProviderSelection Component="Components.ICON_FINDER_ASSISTANT" Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.IconFinder.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.IconFinder.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.IconFinder.PreselectedProvider = selectedValue)"/>
|
||||
<ConfigurationProviderSelection Component="Components.ICON_FINDER_ASSISTANT" Data="@this.availableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.IconFinder.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.IconFinder.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.IconFinder.PreselectedProvider = selectedValue)"/>
|
||||
</MudPaper>
|
||||
</ExpansionPanel>
|
||||
|
||||
@@ -233,7 +314,7 @@
|
||||
<ConfigurationText OptionDescription="Preselect another target language" Disabled="@(() => !this.SettingsManager.ConfigurationData.Translation.PreselectOptions)" Icon="@Icons.Material.Filled.Translate" Text="@(() => this.SettingsManager.ConfigurationData.Translation.PreselectOtherLanguage)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.Translation.PreselectOtherLanguage = updatedText)"/>
|
||||
}
|
||||
<ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.Translation.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Translation.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Translation.MinimumProviderConfidence = selectedValue)"/>
|
||||
<ConfigurationProviderSelection Component="Components.TRANSLATION_ASSISTANT" Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.Translation.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Translation.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Translation.PreselectedProvider = selectedValue)"/>
|
||||
<ConfigurationProviderSelection Component="Components.TRANSLATION_ASSISTANT" Data="@this.availableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.Translation.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Translation.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Translation.PreselectedProvider = selectedValue)"/>
|
||||
</MudPaper>
|
||||
</ExpansionPanel>
|
||||
|
||||
@@ -247,7 +328,7 @@
|
||||
<ConfigurationText OptionDescription="Preselect another programming language" Disabled="@(() => !this.SettingsManager.ConfigurationData.Coding.PreselectOptions)" Icon="@Icons.Material.Filled.Code" Text="@(() => this.SettingsManager.ConfigurationData.Coding.PreselectedOtherProgrammingLanguage)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.Coding.PreselectedOtherProgrammingLanguage = updatedText)"/>
|
||||
}
|
||||
<ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.Coding.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Coding.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Coding.MinimumProviderConfidence = selectedValue)"/>
|
||||
<ConfigurationProviderSelection Component="Components.CODING_ASSISTANT" Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.Coding.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Coding.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Coding.PreselectedProvider = selectedValue)"/>
|
||||
<ConfigurationProviderSelection Component="Components.CODING_ASSISTANT" Data="@this.availableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.Coding.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Coding.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Coding.PreselectedProvider = selectedValue)"/>
|
||||
<ConfigurationSelect OptionDescription="Preselect one of your profiles?" Disabled="@(() => !this.SettingsManager.ConfigurationData.Coding.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Coding.PreselectedProfile)" Data="@ConfigurationSelectDataFactory.GetProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Coding.PreselectedProfile = selectedValue)" OptionHelp="Would you like to preselect one of your profiles?"/>
|
||||
</MudPaper>
|
||||
</ExpansionPanel>
|
||||
@@ -269,7 +350,7 @@
|
||||
<ConfigurationText OptionDescription="Preselect your expertise" Disabled="@(() => !this.SettingsManager.ConfigurationData.TextSummarizer.PreselectOptions)" Icon="@Icons.Material.Filled.Person" Text="@(() => this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedExpertInField)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedExpertInField = updatedText)"/>
|
||||
}
|
||||
<ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.TextSummarizer.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.TextSummarizer.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.TextSummarizer.MinimumProviderConfidence = selectedValue)"/>
|
||||
<ConfigurationProviderSelection Component="Components.TEXT_SUMMARIZER_ASSISTANT" Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.TextSummarizer.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedProvider = selectedValue)"/>
|
||||
<ConfigurationProviderSelection Component="Components.TEXT_SUMMARIZER_ASSISTANT" Data="@this.availableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.TextSummarizer.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedProvider = selectedValue)"/>
|
||||
</MudPaper>
|
||||
</ExpansionPanel>
|
||||
|
||||
@@ -298,7 +379,7 @@
|
||||
<ConfigurationText OptionDescription="Preselect another agenda language" Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" Icon="@Icons.Material.Filled.Translate" Text="@(() => this.SettingsManager.ConfigurationData.Agenda.PreselectedOtherLanguage)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.Agenda.PreselectedOtherLanguage = updatedText)"/>
|
||||
}
|
||||
<ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Agenda.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Agenda.MinimumProviderConfidence = selectedValue)"/>
|
||||
<ConfigurationProviderSelection Component="Components.AGENDA_ASSISTANT" Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Agenda.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Agenda.PreselectedProvider = selectedValue)"/>
|
||||
<ConfigurationProviderSelection Component="Components.AGENDA_ASSISTANT" Data="@this.availableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Agenda.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Agenda.PreselectedProvider = selectedValue)"/>
|
||||
<ConfigurationSelect OptionDescription="Preselect one of your profiles?" Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Agenda.PreselectedProfile)" Data="@ConfigurationSelectDataFactory.GetProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Agenda.PreselectedProfile = selectedValue)" OptionHelp="Would you like to preselect one of your profiles?"/>
|
||||
</MudPaper>
|
||||
</ExpansionPanel>
|
||||
@@ -312,7 +393,7 @@
|
||||
<ConfigurationText OptionDescription="Preselect another target language" Disabled="@(() => !this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectOptions)" Icon="@Icons.Material.Filled.Translate" Text="@(() => this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectedOtherLanguage)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectedOtherLanguage = updatedText)"/>
|
||||
}
|
||||
<ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.GrammarSpelling.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.GrammarSpelling.MinimumProviderConfidence = selectedValue)"/>
|
||||
<ConfigurationProviderSelection Component="Components.GRAMMAR_SPELLING_ASSISTANT" Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectedProvider = selectedValue)"/>
|
||||
<ConfigurationProviderSelection Component="Components.GRAMMAR_SPELLING_ASSISTANT" Data="@this.availableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectedProvider = selectedValue)"/>
|
||||
</MudPaper>
|
||||
</ExpansionPanel>
|
||||
|
||||
@@ -327,7 +408,7 @@
|
||||
<ConfigurationSelect OptionDescription="Preselect a writing style" Disabled="@(() => !this.SettingsManager.ConfigurationData.RewriteImprove.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedWritingStyle)" Data="@ConfigurationSelectDataFactory.GetWritingStyles4RewriteData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedWritingStyle = selectedValue)" OptionHelp="Which writing style should be preselected?"/>
|
||||
<ConfigurationSelect OptionDescription="Preselect a sentence structure" Disabled="@(() => !this.SettingsManager.ConfigurationData.RewriteImprove.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedSentenceStructure)" Data="@ConfigurationSelectDataFactory.GetSentenceStructureData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedSentenceStructure = selectedValue)" OptionHelp="Which voice should be preselected for the sentence structure?"/>
|
||||
<ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.RewriteImprove.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.RewriteImprove.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.RewriteImprove.MinimumProviderConfidence = selectedValue)"/>
|
||||
<ConfigurationProviderSelection Component="Components.REWRITE_ASSISTANT" Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.RewriteImprove.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedProvider = selectedValue)"/>
|
||||
<ConfigurationProviderSelection Component="Components.REWRITE_ASSISTANT" Data="@this.availableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.RewriteImprove.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedProvider = selectedValue)"/>
|
||||
</MudPaper>
|
||||
</ExpansionPanel>
|
||||
|
||||
@@ -343,7 +424,7 @@
|
||||
}
|
||||
<ConfigurationSelect OptionDescription="Preselect a writing style" Disabled="@(() => !this.SettingsManager.ConfigurationData.EMail.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.EMail.PreselectedWritingStyle)" Data="@ConfigurationSelectDataFactory.GetWritingStyles4EMailData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.EMail.PreselectedWritingStyle = selectedValue)" OptionHelp="Which writing style should be preselected?"/>
|
||||
<ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.EMail.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.EMail.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.EMail.MinimumProviderConfidence = selectedValue)"/>
|
||||
<ConfigurationProviderSelection Component="Components.EMAIL_ASSISTANT" Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.EMail.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.EMail.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.EMail.PreselectedProvider = selectedValue)"/>
|
||||
<ConfigurationProviderSelection Component="Components.EMAIL_ASSISTANT" Data="@this.availableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.EMail.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.EMail.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.EMail.PreselectedProvider = selectedValue)"/>
|
||||
<ConfigurationSelect OptionDescription="Preselect one of your profiles?" Disabled="@(() => !this.SettingsManager.ConfigurationData.EMail.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.EMail.PreselectedProfile)" Data="@ConfigurationSelectDataFactory.GetProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.EMail.PreselectedProfile = selectedValue)" OptionHelp="Would you like to preselect one of your profiles?"/>
|
||||
</MudPaper>
|
||||
</ExpansionPanel>
|
||||
@@ -364,7 +445,7 @@
|
||||
<ConfigurationText OptionDescription="Preselect another target language" Disabled="@(() => !this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions)" Icon="@Icons.Material.Filled.Translate" Text="@(() => this.SettingsManager.ConfigurationData.JobPostings.PreselectOtherLanguage)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.JobPostings.PreselectOtherLanguage = updatedText)"/>
|
||||
}
|
||||
<ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.JobPostings.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.JobPostings.MinimumProviderConfidence = selectedValue)"/>
|
||||
<ConfigurationProviderSelection Component="Components.JOB_POSTING_ASSISTANT" Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.JobPostings.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.JobPostings.PreselectedProvider = selectedValue)"/>
|
||||
<ConfigurationProviderSelection Component="Components.JOB_POSTING_ASSISTANT" Data="@this.availableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.JobPostings.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.JobPostings.PreselectedProvider = selectedValue)"/>
|
||||
</MudPaper>
|
||||
</ExpansionPanel>
|
||||
|
||||
@@ -375,7 +456,7 @@
|
||||
<ConfigurationOption OptionDescription="Preselect the web content reader?" Disabled="@(() => !this.SettingsManager.ConfigurationData.LegalCheck.PreselectOptions || this.SettingsManager.ConfigurationData.LegalCheck.HideWebContentReader)" LabelOn="Web content reader is preselected" LabelOff="Web content reader is not preselected" State="@(() => this.SettingsManager.ConfigurationData.LegalCheck.PreselectWebContentReader)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.LegalCheck.PreselectWebContentReader = updatedState)" OptionHelp="When enabled, the web content reader is preselected. This is might be useful when you prefer to load legal content from the web very often."/>
|
||||
<ConfigurationOption OptionDescription="Preselect the content cleaner agent?" Disabled="@(() => !this.SettingsManager.ConfigurationData.LegalCheck.PreselectOptions || this.SettingsManager.ConfigurationData.LegalCheck.HideWebContentReader)" LabelOn="Content cleaner agent is preselected" LabelOff="Content cleaner agent is not preselected" State="@(() => this.SettingsManager.ConfigurationData.LegalCheck.PreselectContentCleanerAgent)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.LegalCheck.PreselectContentCleanerAgent = updatedState)" OptionHelp="When enabled, the content cleaner agent is preselected. This is might be useful when you prefer to clean up the legal content before translating it."/>
|
||||
<ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.LegalCheck.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.LegalCheck.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.LegalCheck.MinimumProviderConfidence = selectedValue)"/>
|
||||
<ConfigurationProviderSelection Component="Components.LEGAL_CHECK_ASSISTANT" Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.LegalCheck.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.LegalCheck.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.LegalCheck.PreselectedProvider = selectedValue)"/>
|
||||
<ConfigurationProviderSelection Component="Components.LEGAL_CHECK_ASSISTANT" Data="@this.availableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.LegalCheck.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.LegalCheck.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.LegalCheck.PreselectedProvider = selectedValue)"/>
|
||||
<ConfigurationSelect OptionDescription="Preselect one of your profiles?" Disabled="@(() => !this.SettingsManager.ConfigurationData.LegalCheck.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.LegalCheck.PreselectedProfile)" Data="@ConfigurationSelectDataFactory.GetProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.LegalCheck.PreselectedProfile = selectedValue)" OptionHelp="Would you like to preselect one of your profiles?"/>
|
||||
</MudPaper>
|
||||
</ExpansionPanel>
|
||||
@@ -389,7 +470,7 @@
|
||||
<ConfigurationText OptionDescription="Preselect another language" Disabled="@(() => !this.SettingsManager.ConfigurationData.Synonyms.PreselectOptions)" Icon="@Icons.Material.Filled.Translate" Text="@(() => this.SettingsManager.ConfigurationData.Synonyms.PreselectedOtherLanguage)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.Synonyms.PreselectedOtherLanguage = updatedText)"/>
|
||||
}
|
||||
<ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.Synonyms.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Synonyms.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Synonyms.MinimumProviderConfidence = selectedValue)"/>
|
||||
<ConfigurationProviderSelection Component="Components.SYNONYMS_ASSISTANT" Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.Synonyms.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Synonyms.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Synonyms.PreselectedProvider = selectedValue)"/>
|
||||
<ConfigurationProviderSelection Component="Components.SYNONYMS_ASSISTANT" Data="@this.availableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.Synonyms.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Synonyms.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Synonyms.PreselectedProvider = selectedValue)"/>
|
||||
</MudPaper>
|
||||
</ExpansionPanel>
|
||||
|
||||
@@ -403,7 +484,7 @@
|
||||
}
|
||||
<ConfigurationSelect OptionDescription="Preselect one of your profiles?" Disabled="@(() => !this.SettingsManager.ConfigurationData.MyTasks.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.MyTasks.PreselectedProfile)" Data="@ConfigurationSelectDataFactory.GetProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.MyTasks.PreselectedProfile = selectedValue)" OptionHelp="Would you like to preselect one of your profiles?"/>
|
||||
<ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.MyTasks.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.MyTasks.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.MyTasks.MinimumProviderConfidence = selectedValue)"/>
|
||||
<ConfigurationProviderSelection Component="Components.MY_TASKS_ASSISTANT" Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.MyTasks.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.MyTasks.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.MyTasks.PreselectedProvider = selectedValue)"/>
|
||||
<ConfigurationProviderSelection Component="Components.MY_TASKS_ASSISTANT" Data="@this.availableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.MyTasks.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.MyTasks.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.MyTasks.PreselectedProvider = selectedValue)"/>
|
||||
</MudPaper>
|
||||
</ExpansionPanel>
|
||||
|
||||
@@ -429,7 +510,7 @@
|
||||
}
|
||||
<ConfigurationSelect OptionDescription="Preselect one of your profiles?" Disabled="@(() => !this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectedProfile)" Data="@ConfigurationSelectDataFactory.GetProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectedProfile = selectedValue)" OptionHelp="Would you like to preselect one of your profiles?"/>
|
||||
<ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.BiasOfTheDay.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.BiasOfTheDay.MinimumProviderConfidence = selectedValue)"/>
|
||||
<ConfigurationProviderSelection Component="Components.BIAS_DAY_ASSISTANT" Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectedProvider = selectedValue)"/>
|
||||
<ConfigurationProviderSelection Component="Components.BIAS_DAY_ASSISTANT" Data="@this.availableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectedProvider = selectedValue)"/>
|
||||
</MudPaper>
|
||||
</ExpansionPanel>
|
||||
|
||||
@@ -440,7 +521,7 @@
|
||||
and attempts to convert relative links into absolute links so that they can be used.
|
||||
</MudText>
|
||||
<ConfigurationOption OptionDescription="Preselect text content cleaner options?" LabelOn="Options are preselected" LabelOff="No options are preselected" State="@(() => this.SettingsManager.ConfigurationData.TextContentCleaner.PreselectAgentOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.TextContentCleaner.PreselectAgentOptions = updatedState)" OptionHelp="When enabled, you can preselect some agent options. This is might be useful when you prefer a LLM."/>
|
||||
<ConfigurationProviderSelection Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.TextContentCleaner.PreselectAgentOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.TextContentCleaner.PreselectedAgentProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.TextContentCleaner.PreselectedAgentProvider = selectedValue)"/>
|
||||
<ConfigurationProviderSelection Data="@this.availableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.TextContentCleaner.PreselectAgentOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.TextContentCleaner.PreselectedAgentProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.TextContentCleaner.PreselectedAgentProvider = selectedValue)"/>
|
||||
</MudPaper>
|
||||
</ExpansionPanel>
|
||||
</MudExpansionPanels>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using AIStudio.Dialogs;
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Settings.DataModel;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
@@ -22,13 +23,11 @@ public partial class Settings : ComponentBase, IMessageBusReceiver, IDisposable
|
||||
[Inject]
|
||||
private MessageBus MessageBus { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private ILogger<Settings> Logger { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private RustService RustService { get; init; } = null!;
|
||||
|
||||
private readonly List<ConfigurationSelectData<string>> availableProviders = new();
|
||||
private readonly List<ConfigurationSelectData<string>> availableLLMProviders = new();
|
||||
private readonly List<ConfigurationSelectData<string>> availableEmbeddingProviders = new();
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
|
||||
@@ -44,16 +43,26 @@ public partial class Settings : ComponentBase, IMessageBusReceiver, IDisposable
|
||||
|
||||
#endregion
|
||||
|
||||
#region Preview-feature related
|
||||
|
||||
private void UpdatePreviewFeatures(PreviewVisibility previewVisibility)
|
||||
{
|
||||
this.SettingsManager.ConfigurationData.App.PreviewVisibility = previewVisibility;
|
||||
this.SettingsManager.ConfigurationData.App.EnabledPreviewFeatures = previewVisibility.FilterPreviewFeatures(this.SettingsManager.ConfigurationData.App.EnabledPreviewFeatures);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Provider related
|
||||
|
||||
private async Task AddProvider()
|
||||
private async Task AddLLMProvider()
|
||||
{
|
||||
var dialogParameters = new DialogParameters<ProviderDialog>
|
||||
{
|
||||
{ x => x.IsEditing, false },
|
||||
};
|
||||
|
||||
var dialogReference = await this.DialogService.ShowAsync<ProviderDialog>("Add Provider", dialogParameters, DialogOptions.FULLSCREEN);
|
||||
var dialogReference = await this.DialogService.ShowAsync<ProviderDialog>("Add LLM Provider", dialogParameters, DialogOptions.FULLSCREEN);
|
||||
var dialogResult = await dialogReference.Result;
|
||||
if (dialogResult is null || dialogResult.Canceled)
|
||||
return;
|
||||
@@ -68,7 +77,7 @@ public partial class Settings : ComponentBase, IMessageBusReceiver, IDisposable
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
|
||||
private async Task EditProvider(AIStudio.Settings.Provider provider)
|
||||
private async Task EditLLMProvider(AIStudio.Settings.Provider provider)
|
||||
{
|
||||
var dialogParameters = new DialogParameters<ProviderDialog>
|
||||
{
|
||||
@@ -83,7 +92,7 @@ public partial class Settings : ComponentBase, IMessageBusReceiver, IDisposable
|
||||
{ x => x.DataHost, provider.Host },
|
||||
};
|
||||
|
||||
var dialogReference = await this.DialogService.ShowAsync<ProviderDialog>("Edit Provider", dialogParameters, DialogOptions.FULLSCREEN);
|
||||
var dialogReference = await this.DialogService.ShowAsync<ProviderDialog>("Edit LLM Provider", dialogParameters, DialogOptions.FULLSCREEN);
|
||||
var dialogResult = await dialogReference.Result;
|
||||
if (dialogResult is null || dialogResult.Canceled)
|
||||
return;
|
||||
@@ -102,20 +111,19 @@ public partial class Settings : ComponentBase, IMessageBusReceiver, IDisposable
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
|
||||
private async Task DeleteProvider(AIStudio.Settings.Provider provider)
|
||||
private async Task DeleteLLMProvider(AIStudio.Settings.Provider provider)
|
||||
{
|
||||
var dialogParameters = new DialogParameters
|
||||
{
|
||||
{ "Message", $"Are you sure you want to delete the provider '{provider.InstanceName}'?" },
|
||||
};
|
||||
|
||||
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Delete Provider", dialogParameters, DialogOptions.FULLSCREEN);
|
||||
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Delete LLM Provider", dialogParameters, DialogOptions.FULLSCREEN);
|
||||
var dialogResult = await dialogReference.Result;
|
||||
if (dialogResult is null || dialogResult.Canceled)
|
||||
return;
|
||||
|
||||
var providerInstance = provider.CreateProvider(this.Logger);
|
||||
var deleteSecretResponse = await this.RustService.DeleteAPIKey(providerInstance);
|
||||
var deleteSecretResponse = await this.RustService.DeleteAPIKey(provider);
|
||||
if(deleteSecretResponse.Success)
|
||||
{
|
||||
this.SettingsManager.ConfigurationData.Providers.Remove(provider);
|
||||
@@ -125,32 +133,8 @@ public partial class Settings : ComponentBase, IMessageBusReceiver, IDisposable
|
||||
this.UpdateProviders();
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
|
||||
private bool HasDashboard(LLMProviders llmProvider) => llmProvider switch
|
||||
{
|
||||
LLMProviders.OPEN_AI => true,
|
||||
LLMProviders.MISTRAL => true,
|
||||
LLMProviders.ANTHROPIC => true,
|
||||
LLMProviders.GROQ => true,
|
||||
LLMProviders.FIREWORKS => true,
|
||||
LLMProviders.GOOGLE => true,
|
||||
|
||||
_ => false,
|
||||
};
|
||||
|
||||
private string GetProviderDashboardURL(LLMProviders llmProvider) => llmProvider switch
|
||||
{
|
||||
LLMProviders.OPEN_AI => "https://platform.openai.com/usage",
|
||||
LLMProviders.MISTRAL => "https://console.mistral.ai/usage/",
|
||||
LLMProviders.ANTHROPIC => "https://console.anthropic.com/settings/plans",
|
||||
LLMProviders.GROQ => "https://console.groq.com/settings/usage",
|
||||
LLMProviders.GOOGLE => "https://console.cloud.google.com/billing",
|
||||
LLMProviders.FIREWORKS => "https://fireworks.ai/account/billing",
|
||||
|
||||
_ => string.Empty,
|
||||
};
|
||||
|
||||
private string GetProviderModelName(AIStudio.Settings.Provider provider)
|
||||
private string GetLLMProviderModelName(AIStudio.Settings.Provider provider)
|
||||
{
|
||||
const int MAX_LENGTH = 36;
|
||||
var modelName = provider.Model.ToString();
|
||||
@@ -159,9 +143,9 @@ public partial class Settings : ComponentBase, IMessageBusReceiver, IDisposable
|
||||
|
||||
private void UpdateProviders()
|
||||
{
|
||||
this.availableProviders.Clear();
|
||||
this.availableLLMProviders.Clear();
|
||||
foreach (var provider in this.SettingsManager.ConfigurationData.Providers)
|
||||
this.availableProviders.Add(new (provider.InstanceName, provider.Id));
|
||||
this.availableLLMProviders.Add(new (provider.InstanceName, provider.Id));
|
||||
}
|
||||
|
||||
private string GetCurrentConfidenceLevelName(LLMProviders llmProvider)
|
||||
@@ -188,6 +172,103 @@ public partial class Settings : ComponentBase, IMessageBusReceiver, IDisposable
|
||||
|
||||
#endregion
|
||||
|
||||
#region Embedding provider related
|
||||
|
||||
private string GetEmbeddingProviderModelName(EmbeddingProvider provider)
|
||||
{
|
||||
const int MAX_LENGTH = 36;
|
||||
var modelName = provider.Model.ToString();
|
||||
return modelName.Length > MAX_LENGTH ? "[...] " + modelName[^Math.Min(MAX_LENGTH, modelName.Length)..] : modelName;
|
||||
}
|
||||
|
||||
private async Task AddEmbeddingProvider()
|
||||
{
|
||||
var dialogParameters = new DialogParameters<EmbeddingDialog>
|
||||
{
|
||||
{ x => x.IsEditing, false },
|
||||
};
|
||||
|
||||
var dialogReference = await this.DialogService.ShowAsync<EmbeddingDialog>("Add Embedding Provider", dialogParameters, DialogOptions.FULLSCREEN);
|
||||
var dialogResult = await dialogReference.Result;
|
||||
if (dialogResult is null || dialogResult.Canceled)
|
||||
return;
|
||||
|
||||
var addedEmbedding = (EmbeddingProvider)dialogResult.Data!;
|
||||
addedEmbedding = addedEmbedding with { Num = this.SettingsManager.ConfigurationData.NextEmbeddingNum++ };
|
||||
|
||||
this.SettingsManager.ConfigurationData.EmbeddingProviders.Add(addedEmbedding);
|
||||
this.UpdateEmbeddingProviders();
|
||||
|
||||
await this.SettingsManager.StoreSettings();
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
|
||||
private async Task EditEmbeddingProvider(EmbeddingProvider embeddingProvider)
|
||||
{
|
||||
var dialogParameters = new DialogParameters<EmbeddingDialog>
|
||||
{
|
||||
{ x => x.DataNum, embeddingProvider.Num },
|
||||
{ x => x.DataId, embeddingProvider.Id },
|
||||
{ x => x.DataName, embeddingProvider.Name },
|
||||
{ x => x.DataLLMProvider, embeddingProvider.UsedLLMProvider },
|
||||
{ x => x.DataModel, embeddingProvider.Model },
|
||||
{ x => x.DataHostname, embeddingProvider.Hostname },
|
||||
{ x => x.IsSelfHosted, embeddingProvider.IsSelfHosted },
|
||||
{ x => x.IsEditing, true },
|
||||
{ x => x.DataHost, embeddingProvider.Host },
|
||||
};
|
||||
|
||||
var dialogReference = await this.DialogService.ShowAsync<EmbeddingDialog>("Edit Embedding Provider", dialogParameters, DialogOptions.FULLSCREEN);
|
||||
var dialogResult = await dialogReference.Result;
|
||||
if (dialogResult is null || dialogResult.Canceled)
|
||||
return;
|
||||
|
||||
var editedEmbeddingProvider = (EmbeddingProvider)dialogResult.Data!;
|
||||
|
||||
// Set the provider number if it's not set. This is important for providers
|
||||
// added before we started saving the provider number.
|
||||
if(editedEmbeddingProvider.Num == 0)
|
||||
editedEmbeddingProvider = editedEmbeddingProvider with { Num = this.SettingsManager.ConfigurationData.NextEmbeddingNum++ };
|
||||
|
||||
this.SettingsManager.ConfigurationData.EmbeddingProviders[this.SettingsManager.ConfigurationData.EmbeddingProviders.IndexOf(embeddingProvider)] = editedEmbeddingProvider;
|
||||
this.UpdateEmbeddingProviders();
|
||||
|
||||
await this.SettingsManager.StoreSettings();
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
|
||||
private async Task DeleteEmbeddingProvider(EmbeddingProvider provider)
|
||||
{
|
||||
var dialogParameters = new DialogParameters
|
||||
{
|
||||
{ "Message", $"Are you sure you want to delete the embedding provider '{provider.Name}'?" },
|
||||
};
|
||||
|
||||
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Delete Embedding Provider", dialogParameters, DialogOptions.FULLSCREEN);
|
||||
var dialogResult = await dialogReference.Result;
|
||||
if (dialogResult is null || dialogResult.Canceled)
|
||||
return;
|
||||
|
||||
var deleteSecretResponse = await this.RustService.DeleteAPIKey(provider);
|
||||
if(deleteSecretResponse.Success)
|
||||
{
|
||||
this.SettingsManager.ConfigurationData.EmbeddingProviders.Remove(provider);
|
||||
await this.SettingsManager.StoreSettings();
|
||||
}
|
||||
|
||||
this.UpdateEmbeddingProviders();
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
|
||||
private void UpdateEmbeddingProviders()
|
||||
{
|
||||
this.availableEmbeddingProviders.Clear();
|
||||
foreach (var provider in this.SettingsManager.ConfigurationData.EmbeddingProviders)
|
||||
this.availableEmbeddingProviders.Add(new (provider.Name, provider.Id));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Profile related
|
||||
|
||||
private async Task AddProfile()
|
||||
|
||||
@@ -36,6 +36,13 @@
|
||||
<Supporter Name="richard-stanton" Type="SupporterType.INDIVIDUAL" URL="https://github.com/richard-stanton" Acknowledgment="Thank you, Richard, for being the first."/>
|
||||
<Supporter Name="peerschuett" Type="SupporterType.INDIVIDUAL" URL="https://github.com/peerschuett" Acknowledgment="Thank you, Peer, for your courage in being the second person to support the project financially."/>
|
||||
</MudList>
|
||||
|
||||
<MudText Typo="Typo.h6" Class="mt-8">
|
||||
The first 10 supporters who make a one-time contribution:
|
||||
</MudText>
|
||||
<MudList T="string">
|
||||
<Supporter Name="Bessieres" Type="SupporterType.INDIVIDUAL" URL="https://github.com/Bessieres" Acknowledgment="Thank you for being the first to contribute a one-time donation."/>
|
||||
</MudList>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
@attribute [Route(Routes.WRITER)]
|
||||
@inherits MSGComponentBase
|
||||
|
||||
<MudText Typo="Typo.h3" Class="mb-2 mr-3">
|
||||
Writer
|
||||
</MudText>
|
||||
|
||||
<PreviewExperimental/>
|
||||
|
||||
<ProviderSelection @bind-ProviderSettings="@this.providerSettings"/>
|
||||
<InnerScrolling HeaderHeight="12.3em">
|
||||
<ChildContent>
|
||||
<MudTextField
|
||||
@ref="@this.textField"
|
||||
T="string"
|
||||
Label="Write your text"
|
||||
@bind-Text="@this.userInput"
|
||||
Immediate="@true"
|
||||
Lines="16"
|
||||
MaxLines="16"
|
||||
Typo="Typo.body1"
|
||||
Variant="Variant.Outlined"
|
||||
InputMode="InputMode.text"
|
||||
FullWidth="@true"
|
||||
OnKeyDown="@this.InputKeyEvent"
|
||||
UserAttributes="@USER_INPUT_ATTRIBUTES"/>
|
||||
|
||||
<MudTextField
|
||||
T="string"
|
||||
Label="Your stage directions"
|
||||
@bind-Text="@this.userDirection"
|
||||
Immediate="@true"
|
||||
Lines="4"
|
||||
MaxLines="4"
|
||||
Typo="Typo.body1"
|
||||
Variant="Variant.Outlined"
|
||||
InputMode="InputMode.text"
|
||||
FullWidth="@true"
|
||||
UserAttributes="@USER_INPUT_ATTRIBUTES"/>
|
||||
</ChildContent>
|
||||
<FooterContent>
|
||||
@if (this.isStreaming)
|
||||
{
|
||||
<MudProgressLinear Color="Color.Primary" Indeterminate="true" Class="mb-6" />
|
||||
}
|
||||
<MudTextField
|
||||
T="string"
|
||||
Label="Suggestion"
|
||||
@bind-Text="@this.suggestion"
|
||||
ReadOnly="@true"
|
||||
Lines="3"
|
||||
Typo="Typo.body1"
|
||||
Variant="Variant.Outlined"
|
||||
FullWidth="@true"
|
||||
UserAttributes="@USER_INPUT_ATTRIBUTES"/>
|
||||
</FooterContent>
|
||||
</InnerScrolling>
|
||||
@@ -0,0 +1,177 @@
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Components;
|
||||
using AIStudio.Provider;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
|
||||
using Timer = System.Timers.Timer;
|
||||
|
||||
namespace AIStudio.Pages;
|
||||
|
||||
public partial class Writer : MSGComponentBase, IAsyncDisposable
|
||||
{
|
||||
[Inject]
|
||||
private ILogger<Chat> Logger { get; init; } = null!;
|
||||
|
||||
private static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new();
|
||||
private readonly Timer typeTimer = new(TimeSpan.FromMilliseconds(1_500));
|
||||
|
||||
private MudTextField<string> textField = null!;
|
||||
private AIStudio.Settings.Provider providerSettings;
|
||||
private ChatThread? chatThread;
|
||||
private bool isStreaming;
|
||||
private string userInput = string.Empty;
|
||||
private string userDirection = string.Empty;
|
||||
private string suggestion = string.Empty;
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
this.ApplyFilters([], []);
|
||||
this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES);
|
||||
this.typeTimer.Elapsed += async (_, _) => await this.InvokeAsync(this.GetSuggestions);
|
||||
this.typeTimer.AutoReset = false;
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Overrides of MSGComponentBase
|
||||
|
||||
public override Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) where TResult : default where TPayload : default
|
||||
{
|
||||
return Task.FromResult(default(TResult));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private bool IsProviderSelected => this.providerSettings.UsedLLMProvider != LLMProviders.NONE;
|
||||
|
||||
private async Task InputKeyEvent(KeyboardEventArgs keyEvent)
|
||||
{
|
||||
var key = keyEvent.Code.ToLowerInvariant();
|
||||
var isTab = key is "tab";
|
||||
var isModifier = keyEvent.AltKey || keyEvent.CtrlKey || keyEvent.MetaKey || keyEvent.ShiftKey;
|
||||
|
||||
if (isTab && !isModifier)
|
||||
{
|
||||
await this.textField.FocusAsync();
|
||||
this.AcceptNextWord();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isTab && isModifier)
|
||||
{
|
||||
await this.textField.FocusAsync();
|
||||
this.AcceptEntireSuggestion();
|
||||
return;
|
||||
}
|
||||
|
||||
if(!isModifier)
|
||||
{
|
||||
this.typeTimer.Stop();
|
||||
this.typeTimer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task GetSuggestions()
|
||||
{
|
||||
if (!this.IsProviderSelected)
|
||||
return;
|
||||
|
||||
this.chatThread ??= new()
|
||||
{
|
||||
WorkspaceId = Guid.Empty,
|
||||
ChatId = Guid.NewGuid(),
|
||||
Name = string.Empty,
|
||||
Seed = 798798,
|
||||
SystemPrompt = """
|
||||
You are an assistant who helps with writing documents. You receive a sample
|
||||
from a document as input. As output, you provide how the begun sentence could
|
||||
continue. You give exactly one variant, not multiple. If the current sentence
|
||||
is complete, you provide an empty response. You do not ask questions, and you
|
||||
do not repeat the task.
|
||||
""",
|
||||
Blocks = [],
|
||||
};
|
||||
|
||||
var time = DateTimeOffset.Now;
|
||||
this.chatThread.Blocks.Clear();
|
||||
this.chatThread.Blocks.Add(new ContentBlock
|
||||
{
|
||||
Time = time,
|
||||
ContentType = ContentType.TEXT,
|
||||
Role = ChatRole.USER,
|
||||
Content = new ContentText
|
||||
{
|
||||
// We use the maximum 160 characters from the end of the text:
|
||||
Text = this.userInput.Length > 160 ? this.userInput[^160..] : this.userInput,
|
||||
},
|
||||
});
|
||||
|
||||
var aiText = new ContentText
|
||||
{
|
||||
// We have to wait for the remote
|
||||
// for the content stream:
|
||||
InitialRemoteWait = true,
|
||||
};
|
||||
|
||||
this.chatThread?.Blocks.Add(new ContentBlock
|
||||
{
|
||||
Time = time,
|
||||
ContentType = ContentType.TEXT,
|
||||
Role = ChatRole.AI,
|
||||
Content = aiText,
|
||||
});
|
||||
|
||||
this.isStreaming = true;
|
||||
this.StateHasChanged();
|
||||
|
||||
await aiText.CreateFromProviderAsync(this.providerSettings.CreateProvider(this.Logger), this.SettingsManager, this.providerSettings.Model, this.chatThread);
|
||||
this.suggestion = aiText.Text;
|
||||
|
||||
this.isStreaming = false;
|
||||
this.StateHasChanged();
|
||||
}
|
||||
|
||||
private void AcceptEntireSuggestion()
|
||||
{
|
||||
if(this.userInput.Last() != ' ')
|
||||
this.userInput += ' ';
|
||||
|
||||
this.userInput += this.suggestion;
|
||||
this.suggestion = string.Empty;
|
||||
this.StateHasChanged();
|
||||
}
|
||||
|
||||
private void AcceptNextWord()
|
||||
{
|
||||
var words = this.suggestion.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
if(words.Length == 0)
|
||||
return;
|
||||
|
||||
if(this.userInput.Last() != ' ')
|
||||
this.userInput += ' ';
|
||||
|
||||
this.userInput += words[0] + ' ';
|
||||
this.suggestion = string.Join(' ', words.Skip(1));
|
||||
this.StateHasChanged();
|
||||
}
|
||||
|
||||
#region Implementation of IAsyncDisposable
|
||||
|
||||
public ValueTask DisposeAsync()
|
||||
{
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -2,4 +2,5 @@
|
||||
@using AIStudio.Components
|
||||
@using AIStudio.Tools
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using MudBlazor
|
||||
@using MudBlazor
|
||||
@using MudExtensions
|
||||
@@ -7,6 +7,8 @@ using Microsoft.Extensions.Logging.Console;
|
||||
|
||||
using MudBlazor.Services;
|
||||
|
||||
using MudExtensions.Services;
|
||||
|
||||
#if !DEBUG
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
@@ -93,6 +95,7 @@ internal sealed class Program
|
||||
options.FormatterName = TerminalLogger.FORMATTER_NAME;
|
||||
}).AddConsoleFormatter<TerminalLogger, ConsoleFormatterOptions>();
|
||||
|
||||
builder.Services.AddMudExtensions();
|
||||
builder.Services.AddMudServices(config =>
|
||||
{
|
||||
config.SnackbarConfiguration.PositionClass = Defaults.Classes.Position.BottomLeft;
|
||||
|
||||
@@ -7,7 +7,7 @@ using AIStudio.Provider.OpenAI;
|
||||
|
||||
namespace AIStudio.Provider.Anthropic;
|
||||
|
||||
public sealed class ProviderAnthropic(ILogger logger) : BaseProvider("https://api.anthropic.com/v1/", logger), IProvider
|
||||
public sealed class ProviderAnthropic(ILogger logger) : BaseProvider("https://api.anthropic.com/v1/", logger)
|
||||
{
|
||||
private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new()
|
||||
{
|
||||
@@ -16,12 +16,12 @@ public sealed class ProviderAnthropic(ILogger logger) : BaseProvider("https://ap
|
||||
|
||||
#region Implementation of IProvider
|
||||
|
||||
public string Id => "Anthropic";
|
||||
public override string Id => LLMProviders.ANTHROPIC.ToName();
|
||||
|
||||
public string InstanceName { get; set; } = "Anthropic";
|
||||
public override string InstanceName { get; set; } = "Anthropic";
|
||||
|
||||
/// <inheritdoc />
|
||||
public async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
|
||||
public override async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
@@ -136,14 +136,14 @@ public sealed class ProviderAnthropic(ILogger logger) : BaseProvider("https://ap
|
||||
|
||||
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
/// <inheritdoc />
|
||||
public async IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
|
||||
public override async IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
public override Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(new[]
|
||||
{
|
||||
@@ -162,13 +162,17 @@ public sealed class ProviderAnthropic(ILogger logger) : BaseProvider("https://ap
|
||||
}.AsEnumerable());
|
||||
}
|
||||
|
||||
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
/// <inheritdoc />
|
||||
public Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
public override Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(Enumerable.Empty<Model>());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(Enumerable.Empty<Model>());
|
||||
}
|
||||
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
using AIStudio.Chat;
|
||||
|
||||
using RustService = AIStudio.Tools.RustService;
|
||||
|
||||
namespace AIStudio.Provider;
|
||||
@@ -5,10 +7,10 @@ namespace AIStudio.Provider;
|
||||
/// <summary>
|
||||
/// The base class for all providers.
|
||||
/// </summary>
|
||||
public abstract class BaseProvider
|
||||
public abstract class BaseProvider : IProvider, ISecretId
|
||||
{
|
||||
/// <summary>
|
||||
/// The HTTP client to use for all requests.
|
||||
/// The HTTP client to use it for all requests.
|
||||
/// </summary>
|
||||
protected readonly HttpClient httpClient = new();
|
||||
|
||||
@@ -39,4 +41,37 @@ public abstract class BaseProvider
|
||||
// Set the base URL:
|
||||
this.httpClient.BaseAddress = new(url);
|
||||
}
|
||||
|
||||
#region Handling of IProvider, which all providers must implement
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract string Id { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract string InstanceName { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, CancellationToken token = default);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, CancellationToken token = default);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of ISecretId
|
||||
|
||||
public string SecretId => this.Id;
|
||||
|
||||
public string SecretName => this.InstanceName;
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -7,7 +7,7 @@ using AIStudio.Chat;
|
||||
|
||||
namespace AIStudio.Provider.Fireworks;
|
||||
|
||||
public class ProviderFireworks(ILogger logger) : BaseProvider("https://api.fireworks.ai/inference/v1/", logger), IProvider
|
||||
public class ProviderFireworks(ILogger logger) : BaseProvider("https://api.fireworks.ai/inference/v1/", logger)
|
||||
{
|
||||
private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new()
|
||||
{
|
||||
@@ -17,13 +17,13 @@ public class ProviderFireworks(ILogger logger) : BaseProvider("https://api.firew
|
||||
#region Implementation of IProvider
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Id => "Fireworks.ai";
|
||||
public override string Id => LLMProviders.FIREWORKS.ToName();
|
||||
|
||||
/// <inheritdoc />
|
||||
public string InstanceName { get; set; } = "Fireworks.ai";
|
||||
public override string InstanceName { get; set; } = "Fireworks.ai";
|
||||
|
||||
/// <inheritdoc />
|
||||
public async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
|
||||
public override async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
@@ -138,20 +138,26 @@ public class ProviderFireworks(ILogger logger) : BaseProvider("https://api.firew
|
||||
|
||||
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
/// <inheritdoc />
|
||||
public async IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
|
||||
public override async IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
public override Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(Enumerable.Empty<Model>());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
public override Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(Enumerable.Empty<Model>());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(Enumerable.Empty<Model>());
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ using AIStudio.Provider.OpenAI;
|
||||
|
||||
namespace AIStudio.Provider.Google;
|
||||
|
||||
public class ProviderGoogle(ILogger logger) : BaseProvider("https://generativelanguage.googleapis.com/v1beta/", logger), IProvider
|
||||
public class ProviderGoogle(ILogger logger) : BaseProvider("https://generativelanguage.googleapis.com/v1beta/", logger)
|
||||
{
|
||||
private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new()
|
||||
{
|
||||
@@ -18,13 +18,13 @@ public class ProviderGoogle(ILogger logger) : BaseProvider("https://generativela
|
||||
#region Implementation of IProvider
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Id => "Google";
|
||||
public override string Id => LLMProviders.GOOGLE.ToName();
|
||||
|
||||
/// <inheritdoc />
|
||||
public string InstanceName { get; set; } = "Google Gemini";
|
||||
public override string InstanceName { get; set; } = "Google Gemini";
|
||||
|
||||
/// <inheritdoc />
|
||||
public async IAsyncEnumerable<string> StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
|
||||
public override async IAsyncEnumerable<string> StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
@@ -139,27 +139,44 @@ public class ProviderGoogle(ILogger logger) : BaseProvider("https://generativela
|
||||
|
||||
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
/// <inheritdoc />
|
||||
public async IAsyncEnumerable<ImageURL> StreamImageCompletion(Provider.Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
|
||||
public override async IAsyncEnumerable<ImageURL> StreamImageCompletion(Provider.Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IEnumerable<Provider.Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
public override async Task<IEnumerable<Provider.Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
return this.LoadModels(token, apiKeyProvisional);
|
||||
var modelResponse = await this.LoadModels(token, apiKeyProvisional);
|
||||
if(modelResponse == default)
|
||||
return [];
|
||||
|
||||
return modelResponse.Models.Where(model =>
|
||||
model.Name.StartsWith("models/gemini-", StringComparison.InvariantCultureIgnoreCase))
|
||||
.Select(n => new Provider.Model(n.Name.Replace("models/", string.Empty), n.DisplayName));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IEnumerable<Provider.Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
public override Task<IEnumerable<Provider.Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(Enumerable.Empty<Provider.Model>());
|
||||
}
|
||||
|
||||
public override async Task<IEnumerable<Provider.Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
var modelResponse = await this.LoadModels(token, apiKeyProvisional);
|
||||
if(modelResponse == default)
|
||||
return [];
|
||||
|
||||
return modelResponse.Models.Where(model =>
|
||||
model.Name.StartsWith("models/text-embedding-", StringComparison.InvariantCultureIgnoreCase))
|
||||
.Select(n => new Provider.Model(n.Name.Replace("models/", string.Empty), n.DisplayName));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private async Task<IEnumerable<Provider.Model>> LoadModels(CancellationToken token, string? apiKeyProvisional = null)
|
||||
private async Task<ModelsResponse> LoadModels(CancellationToken token, string? apiKeyProvisional = null)
|
||||
{
|
||||
var secretKey = apiKeyProvisional switch
|
||||
{
|
||||
@@ -170,19 +187,17 @@ public class ProviderGoogle(ILogger logger) : BaseProvider("https://generativela
|
||||
_ => null,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
if (secretKey is null)
|
||||
return [];
|
||||
return default;
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, $"models?key={secretKey}");
|
||||
var response = await this.httpClient.SendAsync(request, token);
|
||||
|
||||
if(!response.IsSuccessStatusCode)
|
||||
return [];
|
||||
return default;
|
||||
|
||||
var modelResponse = await response.Content.ReadFromJsonAsync<ModelsResponse>(token);
|
||||
return modelResponse.Models.Where(model =>
|
||||
model.Name.StartsWith("models/gemini-", StringComparison.InvariantCultureIgnoreCase))
|
||||
.Select(n => new Provider.Model(n.Name.Replace("models/", string.Empty), n.DisplayName));
|
||||
return modelResponse;
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ using AIStudio.Provider.OpenAI;
|
||||
|
||||
namespace AIStudio.Provider.Groq;
|
||||
|
||||
public class ProviderGroq(ILogger logger) : BaseProvider("https://api.groq.com/openai/v1/", logger), IProvider
|
||||
public class ProviderGroq(ILogger logger) : BaseProvider("https://api.groq.com/openai/v1/", logger)
|
||||
{
|
||||
private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new()
|
||||
{
|
||||
@@ -18,13 +18,13 @@ public class ProviderGroq(ILogger logger) : BaseProvider("https://api.groq.com/o
|
||||
#region Implementation of IProvider
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Id => "Groq";
|
||||
public override string Id => LLMProviders.GROQ.ToName();
|
||||
|
||||
/// <inheritdoc />
|
||||
public string InstanceName { get; set; } = "Groq";
|
||||
public override string InstanceName { get; set; } = "Groq";
|
||||
|
||||
/// <inheritdoc />
|
||||
public async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
|
||||
public override async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
@@ -141,23 +141,29 @@ public class ProviderGroq(ILogger logger) : BaseProvider("https://api.groq.com/o
|
||||
|
||||
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
/// <inheritdoc />
|
||||
public async IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
|
||||
public override async IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
public override Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
return this.LoadModels(token, apiKeyProvisional);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
public override Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult<IEnumerable<Model>>(Array.Empty<Model>());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(Enumerable.Empty<Model>());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ public interface IProvider
|
||||
/// The provider's instance name. Useful for multiple instances of the same provider,
|
||||
/// e.g., to distinguish between different OpenAI API keys.
|
||||
/// </summary>
|
||||
public string InstanceName { get; set; }
|
||||
public string InstanceName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Starts a chat completion stream.
|
||||
@@ -53,4 +53,12 @@ public interface IProvider
|
||||
/// <param name="token">The cancellation token.</param>
|
||||
/// <returns>The list of image models.</returns>
|
||||
public Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// Load all possible embedding models that can be used with this provider.
|
||||
/// </summary>
|
||||
/// <param name="apiKeyProvisional">The provisional API key to use. Useful when the user is adding a new provider. When null, the stored API key is used.</param>
|
||||
/// <param name="token">The cancellation token.</param>
|
||||
/// <returns>The list of embedding models.</returns>
|
||||
public Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default);
|
||||
}
|
||||
@@ -7,6 +7,8 @@ using AIStudio.Provider.OpenAI;
|
||||
using AIStudio.Provider.SelfHosted;
|
||||
using AIStudio.Settings;
|
||||
|
||||
using Host = AIStudio.Provider.SelfHosted.Host;
|
||||
|
||||
namespace AIStudio.Provider;
|
||||
|
||||
public static class LLMProvidersExtensions
|
||||
@@ -65,6 +67,35 @@ public static class LLMProvidersExtensions
|
||||
_ => Confidence.UNKNOWN.WithLevel(settingsManager.GetConfiguredConfidenceLevel(llmProvider)),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the specified provider supports embeddings.
|
||||
/// </summary>
|
||||
/// <param name="llmProvider">The provider to check.</param>
|
||||
/// <returns>True if the provider supports embeddings; otherwise, false.</returns>
|
||||
public static bool ProvideEmbeddings(this LLMProviders llmProvider) => llmProvider switch
|
||||
{
|
||||
//
|
||||
// Providers that support embeddings:
|
||||
//
|
||||
LLMProviders.OPEN_AI => true,
|
||||
LLMProviders.MISTRAL => true,
|
||||
LLMProviders.GOOGLE => true,
|
||||
|
||||
//
|
||||
// Providers that do not support embeddings:
|
||||
//
|
||||
LLMProviders.GROQ => false,
|
||||
LLMProviders.ANTHROPIC => false,
|
||||
LLMProviders.FIREWORKS => false,
|
||||
|
||||
//
|
||||
// Self-hosted providers are treated as a special case anyway.
|
||||
//
|
||||
LLMProviders.SELF_HOSTED => true,
|
||||
|
||||
_ => false,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new provider instance based on the provider value.
|
||||
/// </summary>
|
||||
@@ -72,20 +103,36 @@ public static class LLMProvidersExtensions
|
||||
/// <param name="logger">The logger to use.</param>
|
||||
/// <returns>The provider instance.</returns>
|
||||
public static IProvider CreateProvider(this Settings.Provider providerSettings, ILogger logger)
|
||||
{
|
||||
return providerSettings.UsedLLMProvider.CreateProvider(providerSettings.InstanceName, providerSettings.Host, providerSettings.Hostname, logger);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new provider instance based on the embedding provider value.
|
||||
/// </summary>
|
||||
/// <param name="embeddingProviderSettings">The embedding provider settings.</param>
|
||||
/// <param name="logger">The logger to use.</param>
|
||||
/// <returns>The provider instance.</returns>
|
||||
public static IProvider CreateProvider(this EmbeddingProvider embeddingProviderSettings, ILogger logger)
|
||||
{
|
||||
return embeddingProviderSettings.UsedLLMProvider.CreateProvider(embeddingProviderSettings.Name, embeddingProviderSettings.Host, embeddingProviderSettings.Hostname, logger);
|
||||
}
|
||||
|
||||
private static IProvider CreateProvider(this LLMProviders provider, string instanceName, Host host, string hostname, ILogger logger)
|
||||
{
|
||||
try
|
||||
{
|
||||
return providerSettings.UsedLLMProvider switch
|
||||
return provider switch
|
||||
{
|
||||
LLMProviders.OPEN_AI => new ProviderOpenAI(logger) { InstanceName = providerSettings.InstanceName },
|
||||
LLMProviders.ANTHROPIC => new ProviderAnthropic(logger) { InstanceName = providerSettings.InstanceName },
|
||||
LLMProviders.MISTRAL => new ProviderMistral(logger) { InstanceName = providerSettings.InstanceName },
|
||||
LLMProviders.GOOGLE => new ProviderGoogle(logger) { InstanceName = providerSettings.InstanceName },
|
||||
LLMProviders.OPEN_AI => new ProviderOpenAI(logger) { InstanceName = instanceName },
|
||||
LLMProviders.ANTHROPIC => new ProviderAnthropic(logger) { InstanceName = instanceName },
|
||||
LLMProviders.MISTRAL => new ProviderMistral(logger) { InstanceName = instanceName },
|
||||
LLMProviders.GOOGLE => new ProviderGoogle(logger) { InstanceName = instanceName },
|
||||
|
||||
LLMProviders.GROQ => new ProviderGroq(logger) { InstanceName = providerSettings.InstanceName },
|
||||
LLMProviders.FIREWORKS => new ProviderFireworks(logger) { InstanceName = providerSettings.InstanceName },
|
||||
LLMProviders.GROQ => new ProviderGroq(logger) { InstanceName = instanceName },
|
||||
LLMProviders.FIREWORKS => new ProviderFireworks(logger) { InstanceName = instanceName },
|
||||
|
||||
LLMProviders.SELF_HOSTED => new ProviderSelfHosted(logger, providerSettings) { InstanceName = providerSettings.InstanceName },
|
||||
LLMProviders.SELF_HOSTED => new ProviderSelfHosted(logger, host, hostname) { InstanceName = instanceName },
|
||||
|
||||
_ => new NoProvider(),
|
||||
};
|
||||
@@ -96,4 +143,125 @@ public static class LLMProvidersExtensions
|
||||
return new NoProvider();
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetCreationURL(this LLMProviders provider) => provider switch
|
||||
{
|
||||
LLMProviders.OPEN_AI => "https://platform.openai.com/signup",
|
||||
LLMProviders.MISTRAL => "https://console.mistral.ai/",
|
||||
LLMProviders.ANTHROPIC => "https://console.anthropic.com/dashboard",
|
||||
LLMProviders.GOOGLE => "https://console.cloud.google.com/",
|
||||
|
||||
LLMProviders.GROQ => "https://console.groq.com/",
|
||||
LLMProviders.FIREWORKS => "https://fireworks.ai/login",
|
||||
|
||||
_ => string.Empty,
|
||||
};
|
||||
|
||||
public static string GetDashboardURL(this LLMProviders provider) => provider switch
|
||||
{
|
||||
LLMProviders.OPEN_AI => "https://platform.openai.com/usage",
|
||||
LLMProviders.MISTRAL => "https://console.mistral.ai/usage/",
|
||||
LLMProviders.ANTHROPIC => "https://console.anthropic.com/settings/plans",
|
||||
LLMProviders.GROQ => "https://console.groq.com/settings/usage",
|
||||
LLMProviders.GOOGLE => "https://console.cloud.google.com/billing",
|
||||
LLMProviders.FIREWORKS => "https://fireworks.ai/account/billing",
|
||||
|
||||
_ => string.Empty,
|
||||
};
|
||||
|
||||
public static bool HasDashboard(this LLMProviders provider) => provider switch
|
||||
{
|
||||
LLMProviders.OPEN_AI => true,
|
||||
LLMProviders.MISTRAL => true,
|
||||
LLMProviders.ANTHROPIC => true,
|
||||
LLMProviders.GROQ => true,
|
||||
LLMProviders.FIREWORKS => true,
|
||||
LLMProviders.GOOGLE => true,
|
||||
|
||||
_ => false,
|
||||
};
|
||||
|
||||
public static string GetModelsOverviewURL(this LLMProviders provider) => provider switch
|
||||
{
|
||||
LLMProviders.FIREWORKS => "https://fireworks.ai/models?show=Serverless",
|
||||
_ => string.Empty,
|
||||
};
|
||||
|
||||
public static bool IsLLMModelProvidedManually(this LLMProviders provider) => provider switch
|
||||
{
|
||||
LLMProviders.FIREWORKS => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
public static bool IsEmbeddingModelProvidedManually(this LLMProviders provider, Host host) => provider switch
|
||||
{
|
||||
LLMProviders.SELF_HOSTED => host is not Host.LM_STUDIO,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
public static bool IsHostNeeded(this LLMProviders provider) => provider switch
|
||||
{
|
||||
LLMProviders.SELF_HOSTED => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
public static bool IsHostnameNeeded(this LLMProviders provider) => provider switch
|
||||
{
|
||||
LLMProviders.SELF_HOSTED => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
public static bool IsAPIKeyNeeded(this LLMProviders provider, Host host) => provider switch
|
||||
{
|
||||
LLMProviders.OPEN_AI => true,
|
||||
LLMProviders.MISTRAL => true,
|
||||
LLMProviders.ANTHROPIC => true,
|
||||
LLMProviders.GOOGLE => true,
|
||||
|
||||
LLMProviders.GROQ => true,
|
||||
LLMProviders.FIREWORKS => true,
|
||||
|
||||
LLMProviders.SELF_HOSTED => host is Host.OLLAMA,
|
||||
|
||||
_ => false,
|
||||
};
|
||||
|
||||
public static bool ShowRegisterButton(this LLMProviders provider) => provider switch
|
||||
{
|
||||
LLMProviders.OPEN_AI => true,
|
||||
LLMProviders.MISTRAL => true,
|
||||
LLMProviders.ANTHROPIC => true,
|
||||
LLMProviders.GOOGLE => true,
|
||||
|
||||
LLMProviders.GROQ => true,
|
||||
LLMProviders.FIREWORKS => true,
|
||||
|
||||
_ => false,
|
||||
};
|
||||
|
||||
public static bool CanLoadModels(this LLMProviders provider, Host host, string? apiKey)
|
||||
{
|
||||
if (provider is LLMProviders.SELF_HOSTED)
|
||||
{
|
||||
switch (host)
|
||||
{
|
||||
case Host.NONE:
|
||||
case Host.LLAMACPP:
|
||||
default:
|
||||
return false;
|
||||
|
||||
case Host.OLLAMA:
|
||||
case Host.LM_STUDIO:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(provider is LLMProviders.NONE)
|
||||
return false;
|
||||
|
||||
if(string.IsNullOrWhiteSpace(apiKey))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ using AIStudio.Provider.OpenAI;
|
||||
|
||||
namespace AIStudio.Provider.Mistral;
|
||||
|
||||
public sealed class ProviderMistral(ILogger logger) : BaseProvider("https://api.mistral.ai/v1/", logger), IProvider
|
||||
public sealed class ProviderMistral(ILogger logger) : BaseProvider("https://api.mistral.ai/v1/", logger)
|
||||
{
|
||||
private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new()
|
||||
{
|
||||
@@ -17,12 +17,12 @@ public sealed class ProviderMistral(ILogger logger) : BaseProvider("https://api.
|
||||
|
||||
#region Implementation of IProvider
|
||||
|
||||
public string Id => "Mistral";
|
||||
public override string Id => LLMProviders.MISTRAL.ToName();
|
||||
|
||||
public string InstanceName { get; set; } = "Mistral";
|
||||
public override string InstanceName { get; set; } = "Mistral";
|
||||
|
||||
/// <inheritdoc />
|
||||
public async IAsyncEnumerable<string> StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
|
||||
public override async IAsyncEnumerable<string> StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
@@ -140,14 +140,45 @@ public sealed class ProviderMistral(ILogger logger) : BaseProvider("https://api.
|
||||
|
||||
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
/// <inheritdoc />
|
||||
public async IAsyncEnumerable<ImageURL> StreamImageCompletion(Provider.Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
|
||||
public override async IAsyncEnumerable<ImageURL> StreamImageCompletion(Provider.Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IEnumerable<Provider.Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
public override async Task<IEnumerable<Provider.Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
var modelResponse = await this.LoadModelList(apiKeyProvisional, token);
|
||||
if(modelResponse == default)
|
||||
return [];
|
||||
|
||||
return modelResponse.Data.Where(n =>
|
||||
!n.Id.StartsWith("code", StringComparison.InvariantCulture) &&
|
||||
!n.Id.Contains("embed", StringComparison.InvariantCulture))
|
||||
.Select(n => new Provider.Model(n.Id, null));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<IEnumerable<Provider.Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
var modelResponse = await this.LoadModelList(apiKeyProvisional, token);
|
||||
if(modelResponse == default)
|
||||
return [];
|
||||
|
||||
return modelResponse.Data.Where(n => n.Id.Contains("embed", StringComparison.InvariantCulture))
|
||||
.Select(n => new Provider.Model(n.Id, null));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<IEnumerable<Provider.Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(Enumerable.Empty<Provider.Model>());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private async Task<ModelsResponse> LoadModelList(string? apiKeyProvisional, CancellationToken token)
|
||||
{
|
||||
var secretKey = apiKeyProvisional switch
|
||||
{
|
||||
@@ -160,29 +191,16 @@ public sealed class ProviderMistral(ILogger logger) : BaseProvider("https://api.
|
||||
};
|
||||
|
||||
if (secretKey is null)
|
||||
return [];
|
||||
return default;
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "models");
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", secretKey);
|
||||
|
||||
var response = await this.httpClient.SendAsync(request, token);
|
||||
if(!response.IsSuccessStatusCode)
|
||||
return [];
|
||||
return default;
|
||||
|
||||
var modelResponse = await response.Content.ReadFromJsonAsync<ModelsResponse>(token);
|
||||
return modelResponse.Data.Where(n =>
|
||||
!n.Id.StartsWith("code", StringComparison.InvariantCulture) &&
|
||||
!n.Id.Contains("embed", StringComparison.InvariantCulture))
|
||||
.Select(n => new Provider.Model(n.Id, null));
|
||||
return modelResponse;
|
||||
}
|
||||
|
||||
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
/// <inheritdoc />
|
||||
public Task<IEnumerable<Provider.Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(Enumerable.Empty<Provider.Model>());
|
||||
}
|
||||
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -15,6 +15,8 @@ public class NoProvider : IProvider
|
||||
public Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) => Task.FromResult<IEnumerable<Model>>([]);
|
||||
|
||||
public Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default) => Task.FromResult<IEnumerable<Model>>([]);
|
||||
|
||||
public Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default) => Task.FromResult<IEnumerable<Model>>([]);
|
||||
|
||||
public async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatChatThread, [EnumeratorCancellation] CancellationToken token = default)
|
||||
{
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace AIStudio.Provider.OpenAI;
|
||||
/// <summary>
|
||||
/// The OpenAI provider.
|
||||
/// </summary>
|
||||
public sealed class ProviderOpenAI(ILogger logger) : BaseProvider("https://api.openai.com/v1/", logger), IProvider
|
||||
public sealed class ProviderOpenAI(ILogger logger) : BaseProvider("https://api.openai.com/v1/", logger)
|
||||
{
|
||||
private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new()
|
||||
{
|
||||
@@ -20,13 +20,13 @@ public sealed class ProviderOpenAI(ILogger logger) : BaseProvider("https://api.o
|
||||
#region Implementation of IProvider
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Id => "OpenAI";
|
||||
public override string Id => LLMProviders.OPEN_AI.ToName();
|
||||
|
||||
/// <inheritdoc />
|
||||
public string InstanceName { get; set; } = "OpenAI";
|
||||
public override string InstanceName { get; set; } = "OpenAI";
|
||||
|
||||
/// <inheritdoc />
|
||||
public async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
|
||||
public override async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
@@ -144,23 +144,29 @@ public sealed class ProviderOpenAI(ILogger logger) : BaseProvider("https://api.o
|
||||
|
||||
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
/// <inheritdoc />
|
||||
public async IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
|
||||
public override async IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
public override Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
return this.LoadModels(["gpt-", "o1-"], token, apiKeyProvisional);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
public override Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
return this.LoadModels(["dall-e-"], token, apiKeyProvisional);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
return this.LoadModels(["text-embedding-"], token, apiKeyProvisional);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -7,36 +7,4 @@ public enum Host
|
||||
LM_STUDIO,
|
||||
LLAMACPP,
|
||||
OLLAMA,
|
||||
}
|
||||
|
||||
public static class HostExtensions
|
||||
{
|
||||
public static string Name(this Host host) => host switch
|
||||
{
|
||||
Host.NONE => "None",
|
||||
|
||||
Host.LM_STUDIO => "LM Studio",
|
||||
Host.LLAMACPP => "llama.cpp",
|
||||
Host.OLLAMA => "ollama",
|
||||
|
||||
_ => "Unknown",
|
||||
};
|
||||
|
||||
public static string BaseURL(this Host host) => host switch
|
||||
{
|
||||
Host.LM_STUDIO => "/v1/",
|
||||
Host.LLAMACPP => "/v1/",
|
||||
Host.OLLAMA => "/v1/",
|
||||
|
||||
_ => "/v1/",
|
||||
};
|
||||
|
||||
public static string ChatURL(this Host host) => host switch
|
||||
{
|
||||
Host.LM_STUDIO => "chat/completions",
|
||||
Host.LLAMACPP => "chat/completions",
|
||||
Host.OLLAMA => "chat/completions",
|
||||
|
||||
_ => "chat/completions",
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
namespace AIStudio.Provider.SelfHosted;
|
||||
|
||||
public static class HostExtensions
|
||||
{
|
||||
public static string Name(this Host host) => host switch
|
||||
{
|
||||
Host.NONE => "None",
|
||||
|
||||
Host.LM_STUDIO => "LM Studio",
|
||||
Host.LLAMACPP => "llama.cpp",
|
||||
Host.OLLAMA => "ollama",
|
||||
|
||||
_ => "Unknown",
|
||||
};
|
||||
|
||||
public static string BaseURL(this Host host) => host switch
|
||||
{
|
||||
Host.LM_STUDIO => "/v1/",
|
||||
Host.LLAMACPP => "/v1/",
|
||||
Host.OLLAMA => "/v1/",
|
||||
|
||||
_ => "/v1/",
|
||||
};
|
||||
|
||||
public static string ChatURL(this Host host) => host switch
|
||||
{
|
||||
Host.LM_STUDIO => "chat/completions",
|
||||
Host.LLAMACPP => "chat/completions",
|
||||
Host.OLLAMA => "chat/completions",
|
||||
|
||||
_ => "chat/completions",
|
||||
};
|
||||
|
||||
public static bool AreEmbeddingsSupported(this Host host)
|
||||
{
|
||||
switch (host)
|
||||
{
|
||||
case Host.LM_STUDIO:
|
||||
case Host.OLLAMA:
|
||||
return true;
|
||||
|
||||
default:
|
||||
case Host.LLAMACPP:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ using AIStudio.Provider.OpenAI;
|
||||
|
||||
namespace AIStudio.Provider.SelfHosted;
|
||||
|
||||
public sealed class ProviderSelfHosted(ILogger logger, Settings.Provider provider) : BaseProvider($"{provider.Hostname}{provider.Host.BaseURL()}", logger), IProvider
|
||||
public sealed class ProviderSelfHosted(ILogger logger, Host host, string hostname) : BaseProvider($"{hostname}{host.BaseURL()}", logger)
|
||||
{
|
||||
private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new()
|
||||
{
|
||||
@@ -17,12 +17,12 @@ public sealed class ProviderSelfHosted(ILogger logger, Settings.Provider provide
|
||||
|
||||
#region Implementation of IProvider
|
||||
|
||||
public string Id => "Self-hosted";
|
||||
public override string Id => LLMProviders.SELF_HOSTED.ToName();
|
||||
|
||||
public string InstanceName { get; set; } = "Self-hosted";
|
||||
public override string InstanceName { get; set; } = "Self-hosted";
|
||||
|
||||
/// <inheritdoc />
|
||||
public async IAsyncEnumerable<string> StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
|
||||
public override async IAsyncEnumerable<string> StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this, isTrying: true);
|
||||
@@ -70,7 +70,7 @@ public sealed class ProviderSelfHosted(ILogger logger, Settings.Provider provide
|
||||
try
|
||||
{
|
||||
// Build the HTTP post request:
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, provider.Host.ChatURL());
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, host.ChatURL());
|
||||
|
||||
// Set the authorization header:
|
||||
if (requestedSecret.Success)
|
||||
@@ -148,18 +148,18 @@ public sealed class ProviderSelfHosted(ILogger logger, Settings.Provider provide
|
||||
|
||||
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
/// <inheritdoc />
|
||||
public async IAsyncEnumerable<ImageURL> StreamImageCompletion(Provider.Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
|
||||
public override async IAsyncEnumerable<ImageURL> StreamImageCompletion(Provider.Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
|
||||
|
||||
public async Task<IEnumerable<Provider.Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
public override async Task<IEnumerable<Provider.Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (provider.Host)
|
||||
switch (host)
|
||||
{
|
||||
case Host.LLAMACPP:
|
||||
// Right now, llama.cpp only supports one model.
|
||||
@@ -168,27 +168,7 @@ public sealed class ProviderSelfHosted(ILogger logger, Settings.Provider provide
|
||||
|
||||
case Host.LM_STUDIO:
|
||||
case Host.OLLAMA:
|
||||
|
||||
var secretKey = apiKeyProvisional switch
|
||||
{
|
||||
not null => apiKeyProvisional,
|
||||
_ => await RUST_SERVICE.GetAPIKey(this, isTrying: true) switch
|
||||
{
|
||||
{ Success: true } result => await result.Secret.Decrypt(ENCRYPTION),
|
||||
_ => null,
|
||||
}
|
||||
};
|
||||
|
||||
var lmStudioRequest = new HttpRequestMessage(HttpMethod.Get, "models");
|
||||
if(secretKey is not null)
|
||||
lmStudioRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKeyProvisional);
|
||||
|
||||
var lmStudioResponse = await this.httpClient.SendAsync(lmStudioRequest, token);
|
||||
if(!lmStudioResponse.IsSuccessStatusCode)
|
||||
return [];
|
||||
|
||||
var lmStudioModelResponse = await lmStudioResponse.Content.ReadFromJsonAsync<ModelsResponse>(token);
|
||||
return lmStudioModelResponse.Data.Select(n => new Provider.Model(n.Id, null));
|
||||
return await this.LoadModels(["embed"], [], token, apiKeyProvisional);
|
||||
}
|
||||
|
||||
return [];
|
||||
@@ -200,13 +180,58 @@ public sealed class ProviderSelfHosted(ILogger logger, Settings.Provider provide
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
/// <inheritdoc />
|
||||
public Task<IEnumerable<Provider.Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
public override Task<IEnumerable<Provider.Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(Enumerable.Empty<Provider.Model>());
|
||||
}
|
||||
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
|
||||
public override async Task<IEnumerable<Provider.Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (host)
|
||||
{
|
||||
case Host.LM_STUDIO:
|
||||
case Host.OLLAMA:
|
||||
return await this.LoadModels([], ["embed"], token, apiKeyProvisional);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
this.logger.LogError($"Failed to load text models from self-hosted provider: {e.Message}");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private async Task<IEnumerable<Provider.Model>> LoadModels(string[] ignorePhrases, string[] filterPhrases, CancellationToken token, string? apiKeyProvisional = null)
|
||||
{
|
||||
var secretKey = apiKeyProvisional switch
|
||||
{
|
||||
not null => apiKeyProvisional,
|
||||
_ => await RUST_SERVICE.GetAPIKey(this, isTrying: true) switch
|
||||
{
|
||||
{ Success: true } result => await result.Secret.Decrypt(ENCRYPTION),
|
||||
_ => null,
|
||||
}
|
||||
};
|
||||
|
||||
var lmStudioRequest = new HttpRequestMessage(HttpMethod.Get, "models");
|
||||
if(secretKey is not null)
|
||||
lmStudioRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKeyProvisional);
|
||||
|
||||
var lmStudioResponse = await this.httpClient.SendAsync(lmStudioRequest, token);
|
||||
if(!lmStudioResponse.IsSuccessStatusCode)
|
||||
return [];
|
||||
|
||||
var lmStudioModelResponse = await lmStudioResponse.Content.ReadFromJsonAsync<ModelsResponse>(token);
|
||||
return lmStudioModelResponse.Data.
|
||||
Where(model => !ignorePhrases.Any(ignorePhrase => model.Id.Contains(ignorePhrase, StringComparison.InvariantCulture)) &&
|
||||
filterPhrases.All( filter => model.Id.Contains(filter, StringComparison.InvariantCulture)))
|
||||
.Select(n => new Provider.Model(n.Id, null));
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ public sealed partial class Routes
|
||||
public const string ASSISTANTS = "/assistants";
|
||||
public const string SETTINGS = "/settings";
|
||||
public const string SUPPORTERS = "/supporters";
|
||||
public const string WRITER = "/writer";
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
public const string ASSISTANT_TRANSLATION = "/assistant/translation";
|
||||
|
||||
@@ -25,6 +25,19 @@ public readonly record struct ConfigurationSelectData<T>(string Name, T Value);
|
||||
/// </summary>
|
||||
public static class ConfigurationSelectDataFactory
|
||||
{
|
||||
public static IEnumerable<ConfigurationSelectData<LoadingChatProviderBehavior>> GetLoadingChatProviderBehavior()
|
||||
{
|
||||
yield return new("When possible, use the LLM provider which was used for each chat in the first place", LoadingChatProviderBehavior.USE_CHAT_PROVIDER_IF_AVAILABLE);
|
||||
yield return new("Use the latest LLM provider, which was used before; use the default chat provider initially", LoadingChatProviderBehavior.ALWAYS_USE_LATEST_CHAT_PROVIDER);
|
||||
yield return new("Always use the default chat provider when loading chats", LoadingChatProviderBehavior.ALWAYS_USE_DEFAULT_CHAT_PROVIDER);
|
||||
}
|
||||
|
||||
public static IEnumerable<ConfigurationSelectData<AddChatProviderBehavior>> GetAddChatProviderBehavior()
|
||||
{
|
||||
yield return new("Use the latest LLM provider, which was used before; use the default chat provider initially", AddChatProviderBehavior.ADDED_CHATS_USE_LATEST_PROVIDER);
|
||||
yield return new("Always use the default chat provider for new chats", AddChatProviderBehavior.ADDED_CHATS_USE_DEFAULT_PROVIDER);
|
||||
}
|
||||
|
||||
public static IEnumerable<ConfigurationSelectData<SendBehavior>> GetSendBehaviorData()
|
||||
{
|
||||
yield return new("No key is sending the input", SendBehavior.NO_KEY_IS_SENDING);
|
||||
@@ -64,6 +77,22 @@ public static class ConfigurationSelectDataFactory
|
||||
yield return new("Toggle the sidebar: show the workspaces next to the chat when desired", WorkspaceDisplayBehavior.TOGGLE_SIDEBAR);
|
||||
yield return new("Sidebar is always visible: show the workspaces next to the chat all the time", WorkspaceDisplayBehavior.SIDEBAR_ALWAYS_VISIBLE);
|
||||
}
|
||||
|
||||
public static IEnumerable<ConfigurationSelectData<PreviewVisibility>> GetPreviewVisibility()
|
||||
{
|
||||
yield return new("All preview features are hidden", PreviewVisibility.NONE);
|
||||
yield return new("Also show features ready for release; these should be stable", PreviewVisibility.RELEASE_CANDIDATE);
|
||||
yield return new("Also show features in beta: these are almost ready for release; expect some bugs", PreviewVisibility.BETA);
|
||||
yield return new("Also show features in alpha: these are in development; expect bugs and missing features", PreviewVisibility.ALPHA);
|
||||
yield return new("Show also prototype features: these are works in progress; expect bugs and missing features", PreviewVisibility.PROTOTYPE);
|
||||
yield return new("Show also experimental features: these are experimental; expect bugs, missing features, many changes", PreviewVisibility.EXPERIMENTAL);
|
||||
}
|
||||
|
||||
public static IEnumerable<ConfigurationSelectData<PreviewFeatures>> GetPreviewFeaturesData(SettingsManager settingsManager)
|
||||
{
|
||||
foreach (var source in settingsManager.ConfigurationData.App.PreviewVisibility.GetPreviewFeatures())
|
||||
yield return new(source.GetPreviewDescription(), source);
|
||||
}
|
||||
|
||||
public static IEnumerable<ConfigurationSelectData<NavBehavior>> GetNavBehaviorData()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace AIStudio.Settings.DataModel;
|
||||
|
||||
public enum AddChatProviderBehavior
|
||||
{
|
||||
ADDED_CHATS_USE_DEFAULT_PROVIDER = 0,
|
||||
ADDED_CHATS_USE_LATEST_PROVIDER,
|
||||
}
|
||||
@@ -5924,18 +5924,12 @@ public static class BiasCatalog
|
||||
{
|
||||
if(usedBias.Count >= ALL_BIAS.Count)
|
||||
usedBias.Clear();
|
||||
|
||||
int randomBiasIndex;
|
||||
lock (RANDOM)
|
||||
{
|
||||
randomBiasIndex = RANDOM.Next(0, ALL_BIAS.Count);
|
||||
while(usedBias.Contains(randomBiasIndex))
|
||||
randomBiasIndex = RANDOM.Next(0, ALL_BIAS.Count);
|
||||
}
|
||||
|
||||
var randomBiasIndex = Random.Shared.Next(0, ALL_BIAS.Count);
|
||||
while(usedBias.Contains(randomBiasIndex))
|
||||
randomBiasIndex = Random.Shared.Next(0, ALL_BIAS.Count);
|
||||
|
||||
usedBias.Add(randomBiasIndex);
|
||||
return ALL_BIAS.Values.ElementAt(randomBiasIndex);
|
||||
}
|
||||
|
||||
private static readonly Random RANDOM = new();
|
||||
}
|
||||
@@ -20,6 +20,11 @@ public sealed class Data
|
||||
/// Settings concerning the LLM providers.
|
||||
/// </summary>
|
||||
public DataLLMProviders LLMProviders { get; init; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// A collection of embedding providers configured.
|
||||
/// </summary>
|
||||
public List<EmbeddingProvider> EmbeddingProviders { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// List of configured profiles.
|
||||
@@ -31,6 +36,11 @@ public sealed class Data
|
||||
/// </summary>
|
||||
public uint NextProviderNum { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// The next embedding number to use.
|
||||
/// </summary>
|
||||
public uint NextEmbeddingNum { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// The next profile number to use.
|
||||
/// </summary>
|
||||
|
||||
@@ -27,6 +27,16 @@ public sealed class DataApp
|
||||
/// The navigation behavior.
|
||||
/// </summary>
|
||||
public NavBehavior NavigationBehavior { get; set; } = NavBehavior.NEVER_EXPAND_USE_TOOLTIPS;
|
||||
|
||||
/// <summary>
|
||||
/// The visibility setting for previews features.
|
||||
/// </summary>
|
||||
public PreviewVisibility PreviewVisibility { get; set; } = PreviewVisibility.NONE;
|
||||
|
||||
/// <summary>
|
||||
/// The enabled preview features.
|
||||
/// </summary>
|
||||
public HashSet<PreviewFeatures> EnabledPreviewFeatures { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Should we preselect a provider for the entire app?
|
||||
|
||||
@@ -7,6 +7,16 @@ public sealed class DataChat
|
||||
/// </summary>
|
||||
public SendBehavior ShortcutSendBehavior { get; set; } = SendBehavior.ENTER_IS_SENDING;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the provider behavior for loading a chat.
|
||||
/// </summary>
|
||||
public LoadingChatProviderBehavior LoadingProviderBehavior { get; set; } = LoadingChatProviderBehavior.USE_CHAT_PROVIDER_IF_AVAILABLE;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the provider behavior when adding a chat.
|
||||
/// </summary>
|
||||
public AddChatProviderBehavior AddChatProviderBehavior { get; set; } = AddChatProviderBehavior.ADDED_CHATS_USE_LATEST_PROVIDER;
|
||||
|
||||
/// <summary>
|
||||
/// Preselect any chat options?
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace AIStudio.Settings.DataModel;
|
||||
|
||||
public enum LoadingChatProviderBehavior
|
||||
{
|
||||
USE_CHAT_PROVIDER_IF_AVAILABLE = 0,
|
||||
ALWAYS_USE_DEFAULT_CHAT_PROVIDER,
|
||||
ALWAYS_USE_LATEST_CHAT_PROVIDER,
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace AIStudio.Settings.DataModel;
|
||||
|
||||
public static class PreviewFeatureExtensions
|
||||
{
|
||||
public static string GetPreviewDescription(this PreviewFeatures feature) => feature switch
|
||||
{
|
||||
PreviewFeatures.PRE_WRITER_MODE_2024 => "Writer Mode: Experiments about how to write long texts using AI",
|
||||
PreviewFeatures.PRE_RAG_2024 => "RAG: Preview of our RAG implementation where you can refer your files or integrate enterprise data within your company",
|
||||
|
||||
_ => "Unknown preview feature"
|
||||
};
|
||||
|
||||
public static bool IsEnabled(this PreviewFeatures feature, SettingsManager settingsManager) => settingsManager.ConfigurationData.App.EnabledPreviewFeatures.Contains(feature);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace AIStudio.Settings.DataModel;
|
||||
|
||||
public enum PreviewFeatures
|
||||
{
|
||||
//
|
||||
// Important: Never delete any enum value from this list.
|
||||
// We must be able to deserialize old settings files that may contain these values.
|
||||
//
|
||||
PRE_WRITER_MODE_2024,
|
||||
PRE_RAG_2024,
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace AIStudio.Settings.DataModel;
|
||||
|
||||
public enum PreviewVisibility
|
||||
{
|
||||
NONE = 0,
|
||||
|
||||
RELEASE_CANDIDATE,
|
||||
BETA,
|
||||
ALPHA,
|
||||
PROTOTYPE,
|
||||
EXPERIMENTAL,
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
namespace AIStudio.Settings.DataModel;
|
||||
|
||||
public static class PreviewVisibilityExtensions
|
||||
{
|
||||
public static IList<PreviewFeatures> GetPreviewFeatures(this PreviewVisibility visibility)
|
||||
{
|
||||
var features = new List<PreviewFeatures>();
|
||||
if (visibility >= PreviewVisibility.RELEASE_CANDIDATE)
|
||||
{
|
||||
}
|
||||
|
||||
if (visibility >= PreviewVisibility.BETA)
|
||||
{
|
||||
}
|
||||
|
||||
if (visibility >= PreviewVisibility.ALPHA)
|
||||
{
|
||||
}
|
||||
|
||||
if (visibility >= PreviewVisibility.PROTOTYPE)
|
||||
{
|
||||
features.Add(PreviewFeatures.PRE_RAG_2024);
|
||||
}
|
||||
|
||||
if (visibility >= PreviewVisibility.EXPERIMENTAL)
|
||||
{
|
||||
features.Add(PreviewFeatures.PRE_WRITER_MODE_2024);
|
||||
}
|
||||
|
||||
return features;
|
||||
}
|
||||
|
||||
public static HashSet<PreviewFeatures> FilterPreviewFeatures(this PreviewVisibility visibility, HashSet<PreviewFeatures> enabledFeatures)
|
||||
{
|
||||
var filteredFeatures = new HashSet<PreviewFeatures>();
|
||||
var previewFeatures = visibility.GetPreviewFeatures();
|
||||
foreach (var feature in enabledFeatures)
|
||||
{
|
||||
if (previewFeatures.Contains(feature))
|
||||
filteredFeatures.Add(feature);
|
||||
}
|
||||
|
||||
return filteredFeatures;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using AIStudio.Provider;
|
||||
|
||||
using Host = AIStudio.Provider.SelfHosted.Host;
|
||||
|
||||
namespace AIStudio.Settings;
|
||||
|
||||
public readonly record struct EmbeddingProvider(
|
||||
uint Num,
|
||||
string Id,
|
||||
string Name,
|
||||
LLMProviders UsedLLMProvider,
|
||||
Model Model,
|
||||
bool IsSelfHosted = false,
|
||||
string Hostname = "http://localhost:1234",
|
||||
Host Host = Host.NONE) : ISecretId
|
||||
{
|
||||
public override string ToString() => this.Name;
|
||||
|
||||
#region Implementation of ISecretId
|
||||
|
||||
/// <inheritdoc />
|
||||
[JsonIgnore]
|
||||
public string SecretId => this.Id;
|
||||
|
||||
/// <inheritdoc />
|
||||
[JsonIgnore]
|
||||
public string SecretName => this.Name;
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using AIStudio.Provider;
|
||||
|
||||
using Host = AIStudio.Provider.SelfHosted.Host;
|
||||
@@ -22,7 +24,7 @@ public readonly record struct Provider(
|
||||
Model Model,
|
||||
bool IsSelfHosted = false,
|
||||
string Hostname = "http://localhost:1234",
|
||||
Host Host = Host.NONE)
|
||||
Host Host = Host.NONE) : ISecretId
|
||||
{
|
||||
#region Overrides of ValueType
|
||||
|
||||
@@ -40,4 +42,16 @@ public readonly record struct Provider(
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of ISecretId
|
||||
|
||||
/// <inheritdoc />
|
||||
[JsonIgnore]
|
||||
public string SecretId => this.Id;
|
||||
|
||||
/// <inheritdoc />
|
||||
[JsonIgnore]
|
||||
public string SecretName => this.InstanceName;
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -84,6 +84,14 @@ public sealed class SettingsManager(ILogger<SettingsManager> logger)
|
||||
}
|
||||
|
||||
this.ConfigurationData = SettingsMigrations.Migrate(this.logger, settingsVersion, await File.ReadAllTextAsync(settingsPath), JSON_OPTIONS);
|
||||
|
||||
//
|
||||
// We filter the enabled preview features based on the preview visibility.
|
||||
// This is necessary when the app starts up: some preview features may have
|
||||
// been disabled or released from the last time the app was started.
|
||||
//
|
||||
this.ConfigurationData.App.EnabledPreviewFeatures = this.ConfigurationData.App.PreviewVisibility.FilterPreviewFeatures(this.ConfigurationData.App.EnabledPreviewFeatures);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -131,7 +139,7 @@ public sealed class SettingsManager(ILogger<SettingsManager> logger)
|
||||
return minimumLevel;
|
||||
}
|
||||
|
||||
public Provider GetPreselectedProvider(Tools.Components component)
|
||||
public Provider GetPreselectedProvider(Tools.Components component, string? chatProviderId = null)
|
||||
{
|
||||
var minimumLevel = this.GetMinimumConfidenceLevel(component);
|
||||
|
||||
@@ -139,6 +147,14 @@ public sealed class SettingsManager(ILogger<SettingsManager> logger)
|
||||
if (this.ConfigurationData.Providers.Count == 1 && this.ConfigurationData.Providers[0].UsedLLMProvider.GetConfidence(this).Level >= minimumLevel)
|
||||
return this.ConfigurationData.Providers[0];
|
||||
|
||||
// When there is a chat provider, and it has a confidence level that is high enough, we return it:
|
||||
if (chatProviderId is not null && !string.IsNullOrWhiteSpace(chatProviderId))
|
||||
{
|
||||
var chatProvider = this.ConfigurationData.Providers.FirstOrDefault(x => x.Id == chatProviderId);
|
||||
if (chatProvider.UsedLLMProvider.GetConfidence(this).Level >= minimumLevel)
|
||||
return chatProvider;
|
||||
}
|
||||
|
||||
// When there is a component-preselected provider, and it has a confidence level that is high enough, we return it:
|
||||
var preselectedProvider = component.PreselectedProvider(this);
|
||||
if(preselectedProvider != default && preselectedProvider.UsedLLMProvider.GetConfidence(this).Level >= minimumLevel)
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace AIStudio.Tools;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an interface defining a secret identifier.
|
||||
/// </summary>
|
||||
public interface ISecretId
|
||||
{
|
||||
/// <summary>
|
||||
/// The unique ID of the secret.
|
||||
/// </summary>
|
||||
public string SecretId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The instance name of the secret.
|
||||
/// </summary>
|
||||
public string SecretName { get; }
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.Json;
|
||||
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Tools.Rust;
|
||||
|
||||
// ReSharper disable NotAccessedPositionalProperty.Local
|
||||
@@ -255,71 +254,71 @@ public sealed class RustService : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to get the API key for the given provider.
|
||||
/// Try to get the API key for the given secret ID.
|
||||
/// </summary>
|
||||
/// <param name="provider">The provider to get the API key for.</param>
|
||||
/// <param name="secretId">The secret ID to get the API key for.</param>
|
||||
/// <param name="isTrying">Indicates if we are trying to get the API key. In that case, we don't log errors.</param>
|
||||
/// <returns>The requested secret.</returns>
|
||||
public async Task<RequestedSecret> GetAPIKey(IProvider provider, bool isTrying = false)
|
||||
public async Task<RequestedSecret> GetAPIKey(ISecretId secretId, bool isTrying = false)
|
||||
{
|
||||
var secretRequest = new SelectSecretRequest($"provider::{provider.Id}::{provider.InstanceName}::api_key", Environment.UserName, isTrying);
|
||||
var secretRequest = new SelectSecretRequest($"provider::{secretId.SecretId}::{secretId.SecretName}::api_key", Environment.UserName, isTrying);
|
||||
var result = await this.http.PostAsJsonAsync("/secrets/get", secretRequest, this.jsonRustSerializerOptions);
|
||||
if (!result.IsSuccessStatusCode)
|
||||
{
|
||||
if(!isTrying)
|
||||
this.logger!.LogError($"Failed to get the API key for provider '{provider.Id}' due to an API issue: '{result.StatusCode}'");
|
||||
this.logger!.LogError($"Failed to get the API key for secret ID '{secretId.SecretId}' due to an API issue: '{result.StatusCode}'");
|
||||
return new RequestedSecret(false, new EncryptedText(string.Empty), "Failed to get the API key due to an API issue.");
|
||||
}
|
||||
|
||||
var secret = await result.Content.ReadFromJsonAsync<RequestedSecret>(this.jsonRustSerializerOptions);
|
||||
if (!secret.Success && !isTrying)
|
||||
this.logger!.LogError($"Failed to get the API key for provider '{provider.Id}': '{secret.Issue}'");
|
||||
this.logger!.LogError($"Failed to get the API key for secret ID '{secretId.SecretId}': '{secret.Issue}'");
|
||||
|
||||
return secret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to store the API key for the given provider.
|
||||
/// Try to store the API key for the given secret ID.
|
||||
/// </summary>
|
||||
/// <param name="provider">The provider to store the API key for.</param>
|
||||
/// <param name="secretId">The secret ID to store the API key for.</param>
|
||||
/// <param name="key">The API key to store.</param>
|
||||
/// <returns>The store secret response.</returns>
|
||||
public async Task<StoreSecretResponse> SetAPIKey(IProvider provider, string key)
|
||||
public async Task<StoreSecretResponse> SetAPIKey(ISecretId secretId, string key)
|
||||
{
|
||||
var encryptedKey = await this.encryptor!.Encrypt(key);
|
||||
var request = new StoreSecretRequest($"provider::{provider.Id}::{provider.InstanceName}::api_key", Environment.UserName, encryptedKey);
|
||||
var request = new StoreSecretRequest($"provider::{secretId.SecretId}::{secretId.SecretName}::api_key", Environment.UserName, encryptedKey);
|
||||
var result = await this.http.PostAsJsonAsync("/secrets/store", request, this.jsonRustSerializerOptions);
|
||||
if (!result.IsSuccessStatusCode)
|
||||
{
|
||||
this.logger!.LogError($"Failed to store the API key for provider '{provider.Id}' due to an API issue: '{result.StatusCode}'");
|
||||
this.logger!.LogError($"Failed to store the API key for secret ID '{secretId.SecretId}' due to an API issue: '{result.StatusCode}'");
|
||||
return new StoreSecretResponse(false, "Failed to get the API key due to an API issue.");
|
||||
}
|
||||
|
||||
var state = await result.Content.ReadFromJsonAsync<StoreSecretResponse>(this.jsonRustSerializerOptions);
|
||||
if (!state.Success)
|
||||
this.logger!.LogError($"Failed to store the API key for provider '{provider.Id}': '{state.Issue}'");
|
||||
this.logger!.LogError($"Failed to store the API key for secret ID '{secretId.SecretId}': '{state.Issue}'");
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to delete the API key for the given provider.
|
||||
/// Tries to delete the API key for the given secret ID.
|
||||
/// </summary>
|
||||
/// <param name="provider">The provider to delete the API key for.</param>
|
||||
/// <param name="secretId">The secret ID to delete the API key for.</param>
|
||||
/// <returns>The delete secret response.</returns>
|
||||
public async Task<DeleteSecretResponse> DeleteAPIKey(IProvider provider)
|
||||
public async Task<DeleteSecretResponse> DeleteAPIKey(ISecretId secretId)
|
||||
{
|
||||
var request = new SelectSecretRequest($"provider::{provider.Id}::{provider.InstanceName}::api_key", Environment.UserName, false);
|
||||
var request = new SelectSecretRequest($"provider::{secretId.SecretId}::{secretId.SecretName}::api_key", Environment.UserName, false);
|
||||
var result = await this.http.PostAsJsonAsync("/secrets/delete", request, this.jsonRustSerializerOptions);
|
||||
if (!result.IsSuccessStatusCode)
|
||||
{
|
||||
this.logger!.LogError($"Failed to delete the API key for provider '{provider.Id}' due to an API issue: '{result.StatusCode}'");
|
||||
this.logger!.LogError($"Failed to delete the API key for secret ID '{secretId.SecretId}' due to an API issue: '{result.StatusCode}'");
|
||||
return new DeleteSecretResponse{Success = false, WasEntryFound = false, Issue = "Failed to delete the API key due to an API issue."};
|
||||
}
|
||||
|
||||
var state = await result.Content.ReadFromJsonAsync<DeleteSecretResponse>(this.jsonRustSerializerOptions);
|
||||
if (!state.Success)
|
||||
this.logger!.LogError($"Failed to delete the API key for provider '{provider.Id}': '{state.Issue}'");
|
||||
this.logger!.LogError($"Failed to delete the API key for secret ID '{secretId.SecretId}': '{state.Issue}'");
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
using AIStudio.Provider;
|
||||
|
||||
using Host = AIStudio.Provider.SelfHosted.Host;
|
||||
|
||||
namespace AIStudio.Tools.Validation;
|
||||
|
||||
public sealed class ProviderValidation
|
||||
{
|
||||
public Func<LLMProviders> GetProvider { get; init; } = () => LLMProviders.NONE;
|
||||
|
||||
public Func<string> GetAPIKeyStorageIssue { get; init; } = () => string.Empty;
|
||||
|
||||
public Func<string> GetPreviousInstanceName { get; init; } = () => string.Empty;
|
||||
|
||||
public Func<IEnumerable<string>> GetUsedInstanceNames { get; init; } = () => [];
|
||||
|
||||
public Func<Host> GetHost { get; init; } = () => Host.NONE;
|
||||
|
||||
public string? ValidatingHostname(string hostname)
|
||||
{
|
||||
if(this.GetProvider() != LLMProviders.SELF_HOSTED)
|
||||
return null;
|
||||
|
||||
if(string.IsNullOrWhiteSpace(hostname))
|
||||
return "Please enter a hostname, e.g., http://localhost:1234";
|
||||
|
||||
if(!hostname.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase) && !hostname.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase))
|
||||
return "The hostname must start with either http:// or https://";
|
||||
|
||||
if(!Uri.TryCreate(hostname, UriKind.Absolute, out _))
|
||||
return "The hostname is not a valid HTTP(S) URL.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public string? ValidatingAPIKey(string apiKey)
|
||||
{
|
||||
if(this.GetProvider() is LLMProviders.SELF_HOSTED)
|
||||
return null;
|
||||
|
||||
var apiKeyStorageIssue = this.GetAPIKeyStorageIssue();
|
||||
if(!string.IsNullOrWhiteSpace(apiKeyStorageIssue))
|
||||
return apiKeyStorageIssue;
|
||||
|
||||
if(string.IsNullOrWhiteSpace(apiKey))
|
||||
return "Please enter an API key.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public string? ValidatingInstanceName(string instanceName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(instanceName))
|
||||
return "Please enter an instance name.";
|
||||
|
||||
if (instanceName.Length > 40)
|
||||
return "The instance name must not exceed 40 characters.";
|
||||
|
||||
// The instance name must be unique:
|
||||
var lowerInstanceName = instanceName.ToLowerInvariant();
|
||||
if (lowerInstanceName != this.GetPreviousInstanceName() && this.GetUsedInstanceNames().Contains(lowerInstanceName))
|
||||
return "The instance name must be unique; the chosen name is already in use.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public string? ValidatingModel(Model model)
|
||||
{
|
||||
if(this.GetProvider() is LLMProviders.SELF_HOSTED && this.GetHost() == Host.LLAMACPP)
|
||||
return null;
|
||||
|
||||
if (model == default)
|
||||
return "Please select a model.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public string? ValidatingProvider(LLMProviders llmProvider)
|
||||
{
|
||||
if (llmProvider == LLMProviders.NONE)
|
||||
return "Please select a provider.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public string? ValidatingHost(Host host)
|
||||
{
|
||||
if(this.GetProvider() is not LLMProviders.SELF_HOSTED)
|
||||
return null;
|
||||
|
||||
if (host == Host.NONE)
|
||||
return "Please select a host.";
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Dialogs;
|
||||
using AIStudio.Settings;
|
||||
|
||||
namespace AIStudio.Tools;
|
||||
|
||||
public static class WorkspaceBehaviour
|
||||
{
|
||||
public static readonly JsonSerializerOptions JSON_OPTIONS = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
AllowTrailingCommas = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
|
||||
PropertyNameCaseInsensitive = true,
|
||||
Converters =
|
||||
{
|
||||
new JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseUpper),
|
||||
}
|
||||
};
|
||||
|
||||
public static bool IsChatExisting(LoadChat loadChat)
|
||||
{
|
||||
var chatPath = loadChat.WorkspaceId == Guid.Empty
|
||||
? Path.Join(SettingsManager.DataDirectory, "tempChats", loadChat.ChatId.ToString())
|
||||
: Path.Join(SettingsManager.DataDirectory, "workspaces", loadChat.WorkspaceId.ToString(), loadChat.ChatId.ToString());
|
||||
|
||||
return Directory.Exists(chatPath);
|
||||
}
|
||||
|
||||
public static async Task StoreChat(ChatThread chat)
|
||||
{
|
||||
string chatDirectory;
|
||||
if (chat.WorkspaceId == Guid.Empty)
|
||||
chatDirectory = Path.Join(SettingsManager.DataDirectory, "tempChats", chat.ChatId.ToString());
|
||||
else
|
||||
chatDirectory = Path.Join(SettingsManager.DataDirectory, "workspaces", chat.WorkspaceId.ToString(), chat.ChatId.ToString());
|
||||
|
||||
// Ensure the directory exists:
|
||||
Directory.CreateDirectory(chatDirectory);
|
||||
|
||||
// Save the chat name:
|
||||
var chatNamePath = Path.Join(chatDirectory, "name");
|
||||
await File.WriteAllTextAsync(chatNamePath, chat.Name);
|
||||
|
||||
// Save the thread as thread.json:
|
||||
var chatPath = Path.Join(chatDirectory, "thread.json");
|
||||
await File.WriteAllTextAsync(chatPath, JsonSerializer.Serialize(chat, JSON_OPTIONS), Encoding.UTF8);
|
||||
}
|
||||
|
||||
public static async Task<ChatThread?> LoadChat(LoadChat loadChat)
|
||||
{
|
||||
var chatPath = loadChat.WorkspaceId == Guid.Empty
|
||||
? Path.Join(SettingsManager.DataDirectory, "tempChats", loadChat.ChatId.ToString())
|
||||
: Path.Join(SettingsManager.DataDirectory, "workspaces", loadChat.WorkspaceId.ToString(), loadChat.ChatId.ToString());
|
||||
|
||||
if(!Directory.Exists(chatPath))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
var chatData = await File.ReadAllTextAsync(Path.Join(chatPath, "thread.json"), Encoding.UTF8);
|
||||
var chat = JsonSerializer.Deserialize<ChatThread>(chatData, JSON_OPTIONS);
|
||||
return chat;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<string> LoadWorkspaceName(Guid workspaceId)
|
||||
{
|
||||
if(workspaceId == Guid.Empty)
|
||||
return string.Empty;
|
||||
|
||||
var workspacePath = Path.Join(SettingsManager.DataDirectory, "workspaces", workspaceId.ToString());
|
||||
var workspaceNamePath = Path.Join(workspacePath, "name");
|
||||
return await File.ReadAllTextAsync(workspaceNamePath, Encoding.UTF8);
|
||||
}
|
||||
|
||||
public static async Task DeleteChat(IDialogService dialogService, Guid workspaceId, Guid chatId, bool askForConfirmation = true)
|
||||
{
|
||||
var chat = await LoadChat(new(workspaceId, chatId));
|
||||
if (chat is null)
|
||||
return;
|
||||
|
||||
if (askForConfirmation)
|
||||
{
|
||||
var workspaceName = await LoadWorkspaceName(chat.WorkspaceId);
|
||||
var dialogParameters = new DialogParameters
|
||||
{
|
||||
{
|
||||
"Message", (chat.WorkspaceId == Guid.Empty) switch
|
||||
{
|
||||
true => $"Are you sure you want to delete the temporary chat '{chat.Name}'?",
|
||||
false => $"Are you sure you want to delete the chat '{chat.Name}' in the workspace '{workspaceName}'?",
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
var dialogReference = await dialogService.ShowAsync<ConfirmDialog>("Delete Chat", dialogParameters, Dialogs.DialogOptions.FULLSCREEN);
|
||||
var dialogResult = await dialogReference.Result;
|
||||
if (dialogResult is null || dialogResult.Canceled)
|
||||
return;
|
||||
}
|
||||
|
||||
string chatDirectory;
|
||||
if (chat.WorkspaceId == Guid.Empty)
|
||||
chatDirectory = Path.Join(SettingsManager.DataDirectory, "tempChats", chat.ChatId.ToString());
|
||||
else
|
||||
chatDirectory = Path.Join(SettingsManager.DataDirectory, "workspaces", chat.WorkspaceId.ToString(), chat.ChatId.ToString());
|
||||
|
||||
Directory.Delete(chatDirectory, true);
|
||||
}
|
||||
}
|
||||
@@ -63,8 +63,11 @@ def "main fix_web_assets" []: nothing -> nothing {
|
||||
mkdir wwwroot/system
|
||||
|
||||
# Copy the web assets from the first RID to the source project:
|
||||
let source_path: glob = $"bin/release/net8.0/($rid)/publish/wwwroot/_content/*"
|
||||
cp --recursive --force --update $source_path wwwroot/system/
|
||||
let source_paths = glob --depth 99 bin/release/net8.0/($rid)/publish/wwwroot/_content/*
|
||||
|
||||
for source_path in $source_paths {
|
||||
cp --recursive --force --update $source_path wwwroot/system/
|
||||
}
|
||||
}
|
||||
|
||||
def "main publish" []: nothing -> nothing {
|
||||
|
||||
@@ -2,32 +2,46 @@
|
||||
"version": 1,
|
||||
"dependencies": {
|
||||
"net8.0": {
|
||||
"CodeBeam.MudBlazor.Extensions": {
|
||||
"type": "Direct",
|
||||
"requested": "[7.1.0, )",
|
||||
"resolved": "7.1.0",
|
||||
"contentHash": "qbyCT4XMc/lbi2XdkUh9aSPu97RUPDisU7fpTCU0Q4nGywqJsAxrwcpaxJqoycq+uj3smwX5zOn6yzfsHUObeQ==",
|
||||
"dependencies": {
|
||||
"BuildBundlerMinifier": "3.2.449",
|
||||
"CsvHelper": "31.0.3",
|
||||
"Microsoft.AspNetCore.Components": "8.0.11",
|
||||
"Microsoft.AspNetCore.Components.Web": "8.0.11",
|
||||
"MudBlazor": "7.15.0",
|
||||
"ZXing.Net": "0.16.9"
|
||||
}
|
||||
},
|
||||
"HtmlAgilityPack": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.11.70, )",
|
||||
"resolved": "1.11.70",
|
||||
"contentHash": "lwCgdq4H+WXH+lkM7TvJhyEs5uNGzoTud1VmXz2jr30/yOTRtG/oMCour5JMTN0t0fU4uFwFYIrDd98FH2RKsQ=="
|
||||
"requested": "[1.11.71, )",
|
||||
"resolved": "1.11.71",
|
||||
"contentHash": "HlEThQBnob4/29DIiyrPSYlGXXSZUtOH7571PRev+yu8FW8n5pdxqHyun+PMm17cp3ovCrxFhXedC+1HUSdRMA=="
|
||||
},
|
||||
"Microsoft.Extensions.FileProviders.Embedded": {
|
||||
"type": "Direct",
|
||||
"requested": "[8.0.10, )",
|
||||
"resolved": "8.0.10",
|
||||
"contentHash": "N/Ow71LLKXbC4fOFoAG7QNyyhgRypd8in8lc92xInZyyNFlBTgGR3R1BV91Wr5+1/l9+Qvo+2Ee2cAth43OP/Q==",
|
||||
"requested": "[9.0.0, )",
|
||||
"resolved": "9.0.0",
|
||||
"contentHash": "6Ev1goLIvggLF6uCs6oZvdr9JM+2b1Zj+4FLdBWNW5iw3tm2BymVIb0yMsjnQTBWL7YUmqVWH3u45hSqOfvuqg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": "8.0.0"
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": "9.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.NET.ILLink.Tasks": {
|
||||
"type": "Direct",
|
||||
"requested": "[8.0.10, )",
|
||||
"resolved": "8.0.10",
|
||||
"contentHash": "xT8jYjlroY7SLbGtoV9vUTVW/TPgodL4Egc31a444Xe0TMytLZ3UlKQ0kxMZsy/CrWsFB6wtKnSG1SsXcWreew=="
|
||||
"requested": "[8.0.11, )",
|
||||
"resolved": "8.0.11",
|
||||
"contentHash": "zk5lnZrYJgtuJG8L4v17Ej8rZ3PUcR2iweNV08BaO5LbYHIi2wNaVNcJoLxvqgQdnjLlKnCCfVGLDr6QHeAarQ=="
|
||||
},
|
||||
"MudBlazor": {
|
||||
"type": "Direct",
|
||||
"requested": "[7.14.0, )",
|
||||
"resolved": "7.14.0",
|
||||
"contentHash": "hpuNSlI6/BVCe4H6X4BoHijxMffFE1ldunvFzAFZRTGuSGoYC7N6IIUv64MN3F/hkiRllx3gtivTZjivymxIpQ==",
|
||||
"requested": "[7.15.0, )",
|
||||
"resolved": "7.15.0",
|
||||
"contentHash": "b2+hO7ZTGwsGhu9SoowuNe2ETeF1GzExpg/hIYR++rSh+vy4v/QD0iOsrATWv3LdFJXSgk3d1+XIr5srW3jBiA==",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNetCore.Components": "8.0.8",
|
||||
"Microsoft.AspNetCore.Components.Web": "8.0.8",
|
||||
@@ -53,6 +67,16 @@
|
||||
"HtmlAgilityPack": "1.11.61"
|
||||
}
|
||||
},
|
||||
"BuildBundlerMinifier": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.2.449",
|
||||
"contentHash": "uA9sYDy4VepL3xwzBTLcP2LyuVYMt0ZIT3gaSiXvGoX15Ob+rOP+hGydhevlSVd+rFo+Y+VQFEHDuWU8HBW+XA=="
|
||||
},
|
||||
"CsvHelper": {
|
||||
"type": "Transitive",
|
||||
"resolved": "31.0.3",
|
||||
"contentHash": "ygck8DR4mG/VDA/LgIVVGpEtXXPDVaaNZNJGrOAJ4pckVw4MbAQ3n/u6YFDv3bwlQhlxTmPhCyk5E4hxe96Crg=="
|
||||
},
|
||||
"Markdig": {
|
||||
"type": "Transitive",
|
||||
"resolved": "0.37.0",
|
||||
@@ -60,73 +84,73 @@
|
||||
},
|
||||
"Microsoft.AspNetCore.Authorization": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.8",
|
||||
"contentHash": "e+ehkJTx0bqUb9zzM9ohV830LhoK3fy5lfVGPWAozbq6I2rJpyq7L0OUgEGNEQLMTzawkDjpNlJ4WU1ck/TDjw==",
|
||||
"resolved": "8.0.11",
|
||||
"contentHash": "ACaLyjBSz9WUzbaJe0Sv09/FihRNHYlRUIj3uQ8CZFFByf6Qwv3+PXnbltidFKz2iOyqdvppQias3emdQUY2nA==",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNetCore.Metadata": "8.0.8",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "8.0.1",
|
||||
"Microsoft.AspNetCore.Metadata": "8.0.11",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "8.0.2",
|
||||
"Microsoft.Extensions.Options": "8.0.2"
|
||||
}
|
||||
},
|
||||
"Microsoft.AspNetCore.Components": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.8",
|
||||
"contentHash": "VszyvGli+bgbt6gANOPSx8dzHWIH8qrDmWaNzb0GcU8MDKi2eULNW4ga+xyj7ApOYDLZA2V1FL4710Edw0Om4g==",
|
||||
"resolved": "8.0.11",
|
||||
"contentHash": "kyhSQcVEQvMnv2BNRn7JRgYCr+PIO5Uh1mhIFdCNycxE/k8NsI72sV693s1KVmVebMA8g3hTBmfBEheWb3hhww==",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNetCore.Authorization": "8.0.8",
|
||||
"Microsoft.AspNetCore.Components.Analyzers": "8.0.8"
|
||||
"Microsoft.AspNetCore.Authorization": "8.0.11",
|
||||
"Microsoft.AspNetCore.Components.Analyzers": "8.0.11"
|
||||
}
|
||||
},
|
||||
"Microsoft.AspNetCore.Components.Analyzers": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.8",
|
||||
"contentHash": "PKujmeqEO8fY/U5bWoww63yMF4XqjSnrq0cP4fXUyz4JVYTkh+MZEFktfLaoVaGU4JbtUNjJFvEYA96H8lbb8Q=="
|
||||
"resolved": "8.0.11",
|
||||
"contentHash": "4JtFt5IR0ixuFpwY6D2Xi5R+vZQ6iykd2EuG3puHETCOZOgYG8M538LCY1lbgQTkHOL04YKDjQTQu8PU/BaXRQ=="
|
||||
},
|
||||
"Microsoft.AspNetCore.Components.Forms": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.8",
|
||||
"contentHash": "81P0TqjN+Q52QZbMje1Cirb7DOPKBW8CiemLo9x0KITWEGofdNJJECK1FPoMfUkgyaOknuCRQy0L+6G3K6+rPg==",
|
||||
"resolved": "8.0.11",
|
||||
"contentHash": "60g+idqaiVhPVNOqauy/vH5lREpjcuKl3/w6zJhdU1PFWg4jtdoyIPQH+qxBKsUohkELhH3cRfzGRKElVuZuwg==",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNetCore.Components": "8.0.8"
|
||||
"Microsoft.AspNetCore.Components": "8.0.11"
|
||||
}
|
||||
},
|
||||
"Microsoft.AspNetCore.Components.Web": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.8",
|
||||
"contentHash": "PX6JYvx2A5R1cQNLiTMMcm+7zvloVCGxvuRf24Ezaj1aUvZDYnSfyNkD2gyVun7AizGAn+x3QYr4mBfbZ9L5TA==",
|
||||
"resolved": "8.0.11",
|
||||
"contentHash": "IDmjQ/K7hv6zUEz2LsCkQBngZx6PMnty8OdSPf0hYGMpC+4Yi37pgCc/25fFu3CSBe8nDirqTrqKtfToHWCpbw==",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNetCore.Components": "8.0.8",
|
||||
"Microsoft.AspNetCore.Components.Forms": "8.0.8",
|
||||
"Microsoft.Extensions.DependencyInjection": "8.0.0",
|
||||
"Microsoft.AspNetCore.Components": "8.0.11",
|
||||
"Microsoft.AspNetCore.Components.Forms": "8.0.11",
|
||||
"Microsoft.Extensions.DependencyInjection": "8.0.1",
|
||||
"Microsoft.Extensions.Primitives": "8.0.0",
|
||||
"Microsoft.JSInterop": "8.0.8",
|
||||
"Microsoft.JSInterop": "8.0.11",
|
||||
"System.IO.Pipelines": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.AspNetCore.Metadata": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.8",
|
||||
"contentHash": "4WSCv7ddawjabLZ7hj0Ai7hTzFYLw5PsvHVKrJkuBgrv+blELbIDMVj7b5q7PZs7Xvz0W0ViKY/rcVkqD0D5yQ=="
|
||||
"resolved": "8.0.11",
|
||||
"contentHash": "cy04xnMSTXTkRPjEwseRz57R5zjR/CWsdEOHH6NhWbNl97k+U1w6dSjqIOC7kv08tyzmM30FzIilSDtE5HdL/A=="
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "V8S3bsm50ig6JSyrbcJJ8bW2b9QLGouz+G1miK3UTaOWmMtFwNNNzUf4AleyDWUmTrWMLNnFSLEQtxmxgNQnNQ==",
|
||||
"resolved": "8.0.1",
|
||||
"contentHash": "BmANAnR5Xd4Oqw7yQ75xOAYODybZQRzdeNucg7kS5wWKd2PNnMdYtJ2Vciy0QLylRmv42DGl5+AFL9izA6F1Rw==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0"
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.1",
|
||||
"contentHash": "fGLiCRLMYd00JYpClraLjJTNKLmMJPnqxMaiRzEBIIvevlzxz33mXy39Lkd48hu1G+N21S7QpaO5ZzKsI6FRuA=="
|
||||
"resolved": "8.0.2",
|
||||
"contentHash": "3iE7UF7MQkCv1cxzCahz+Y/guQbTqieyxyaWKhrRO91itI9cOKO76OHeQDahqG4MmW5umr3CcCvGmK92lWNlbg=="
|
||||
},
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "ZbaMlhJlpisjuWbvXr4LdAst/1XxH3vZ6A0BsgTphZ2L4PGuxRLz7Jr/S7mkAAnOn78Vu0fKhEgNF5JO3zfjqQ==",
|
||||
"resolved": "9.0.0",
|
||||
"contentHash": "uK439QzYR0q2emLVtYzwyK3x+T5bTY4yWsd/k/ZUS9LR6Sflp8MIdhGXW8kQCd86dQD4tLqvcbLkku8qHY263Q==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Primitives": "8.0.0"
|
||||
"Microsoft.Extensions.Primitives": "9.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Localization": {
|
||||
@@ -147,10 +171,10 @@
|
||||
},
|
||||
"Microsoft.Extensions.Logging.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.1",
|
||||
"contentHash": "RIFgaqoaINxkM2KTOw72dmilDmTrYA0ns2KW4lDz4gZ2+o6IQ894CzmdL3StM2oh7QQq44nCWiqKqc4qUI9Jmg==",
|
||||
"resolved": "8.0.2",
|
||||
"contentHash": "nroMDjS7hNBPtkZqVBbSiQaQjWRDxITI8Y7XnDs97rqG3EbzVTNLZQf7bIeUJcaHOV8bca47s1Uxq94+2oGdxA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.1"
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Options": {
|
||||
@@ -164,20 +188,25 @@
|
||||
},
|
||||
"Microsoft.Extensions.Primitives": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g=="
|
||||
"resolved": "9.0.0",
|
||||
"contentHash": "N3qEBzmLMYiASUlKxxFIISP4AiwuPTHF5uCh+2CWSwwzAJiIYx0kBJsS30cp1nvhSySFAVi30jecD307jV+8Kg=="
|
||||
},
|
||||
"Microsoft.JSInterop": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.8",
|
||||
"contentHash": "vJhGdkYKvytLIMi5J2vpsyfjPKzSm7dnCfdGwjt4skvJcomlJyKDTA3wAwxIMuMbiWcOi1HuiQ6R+85C+qFJrw=="
|
||||
"resolved": "8.0.11",
|
||||
"contentHash": "UYSbAkNGTWVUne3I04/9IRQel3Bt1Ww6Y5cjvZEZ89rWhBD1yWu7YDotvQS62V6mgSfFaXXPGrCUm1VG824QXw=="
|
||||
},
|
||||
"System.IO.Pipelines": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "FHNOatmUq0sqJOkTx+UF/9YK1f180cnW5FVqnQMvYUN0elp6wFzbtPSiqbo1/ru8ICp43JM1i7kKkk6GsNGHlA=="
|
||||
},
|
||||
"ZXing.Net": {
|
||||
"type": "Transitive",
|
||||
"resolved": "0.16.9",
|
||||
"contentHash": "7WaVMHklpT3Ye2ragqRIwlFRsb6kOk63BOGADV0fan3ulVfGLUYkDi5yNUsZS/7FVNkWbtHAlDLmu4WnHGfqvQ=="
|
||||
}
|
||||
},
|
||||
"net8.0/osx-arm64": {}
|
||||
"net8.0/osx-x64": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
# v0.9.19, build 194 (2024-11-14 05:58 UTC)
|
||||
- Added provider's metadata: which providers offer embeddings for RAG? This is in preparation for integrating your data and files.
|
||||
- Added `Bessieres` as a one-time contributor. Thank you for your support.
|
||||
- Fixed a bug regarding hostnames of self-hosted providers, where it was possible to enter spaces at the end.
|
||||
- Upgraded .NET to v8.0.11.
|
||||
- Upgraded HtmlAgilityPack to v1.11.71.
|
||||
- Upgraded MudBlazor to v7.15.0.
|
||||
@@ -0,0 +1,2 @@
|
||||
# v0.9.20, build 195 (2024-11-16 20:44 UTC)
|
||||
- Fixed bugs related to storing and loading chats when the workspaces were docked to the left and were collapsed.
|
||||
@@ -0,0 +1,5 @@
|
||||
# v0.9.21, build 196 (2024-11-23 12:22 UTC)
|
||||
- Added: Chats remember which LLM provider was used. This is useful to continue exactly where you left off.
|
||||
- Added options for how to decide which LLM provider should be chosen when creating new chats and when loading chats. When you want the previous behavior restored (always use the default chat provider), you can configure this behavior as well.
|
||||
- Fixed the missed workspace title when loading the today's bias.
|
||||
- Fixed auto-save when sending assistant results to a new chat.
|
||||
@@ -0,0 +1,7 @@
|
||||
# v0.9.22, build 197 (2024-12-04 10:58 UTC)
|
||||
- Added the possibility to configure preview feature visibility in the app settings. This is useful for users who want to test new features before they are officially released.
|
||||
- Added the possibility to configure embedding providers in the app settings as a prototype preview feature. Embeddings are necessary in order to integrate local data and files.
|
||||
- Added the writer mode as an experimental preview feature. This feature is just an experiment as we explore how to implement long text support in AI Studio.
|
||||
- Improved self-hosted LLM provider configuration by filtering embedding models.
|
||||
- Fixed a bug where you couldn't move a chat to (another) workspace when the workspaces were docked to the left side and were collapsed.
|
||||
- Fixed a bug when you replaced a chat with a new chat, and the workspaces were docked to the left and were collapsed; then, the previous chat was not removed from the workspace.
|
||||
Vendored
Executable
+1
@@ -0,0 +1 @@
|
||||
.mud-combobox{margin:0;padding:0;position:relative;list-style:none;}.mud-combobox.mud-combobox-padding{padding-top:8px;padding-bottom:8px;}.mud-combobox-item{width:100%;display:flex;position:relative;box-sizing:border-box;text-align:start;align-items:center;padding-top:8px;padding-bottom:8px;justify-content:flex-start;text-decoration:none;outline:none;min-height:48px;}.mud-combobox-item.mud-combobox-item-comfort{padding-top:4px;padding-bottom:4px;min-height:40px;}.mud-combobox-item.mud-combobox-item-slim{padding-top:2px;padding-bottom:2px;min-height:32px;}.mud-combobox-item.mud-combobox-item-superslim{padding-top:0;padding-bottom:0;min-height:24px;}.mud-combobox-item.mud-combobox-item-disabled{color:var(--mud-palette-action-disabled) !important;cursor:default !important;pointer-events:none !important;}.mud-combobox-item.mud-combobox-item-disabled .mud-combobox-item-icon{color:var(--mud-palette-action-disabled) !important;}.mud-combobox-item-clickable{color:inherit;border:0;cursor:pointer;margin:0;outline:0;user-select:none;border-radius:0;vertical-align:middle;background-color:transparent;-webkit-appearance:none;-webkit-tap-highlight-color:transparent;transition:background-color 150ms cubic-bezier(.4,0,.2,1) 0ms;}.mud-combobox-item-clickable:hover{background-color:var(--mud-palette-action-default-hover);}.mud-combobox-item-gutters{padding-left:16px;padding-right:16px;}.mud-combobox-item-text{flex:1 1 auto;min-width:0;margin-top:4px;margin-bottom:4px;padding-inline-start:8px;padding-inline-end:8px;}.mud-combobox-item-text-inset{padding-left:56px;padding-inline-start:56px;padding-inline-end:unset;}.mud-combobox-item-icon{color:var(--mud-palette-action-default);display:inline-flex;flex-shrink:0;padding-inline-start:8px;padding-inline-end:8px;margin-inline-start:-4px;margin-inline-end:4px;}.mud-combobox-item-multiselect{max-height:32px;}.mud-combobox-item-multiselect.mud-combobox-item-multiselect-checkbox{padding-inline-end:16px;}.mud-combobox-subheader{color:var(--mud-palette-action-default);background-color:var(--mud-palette-background);font-size:.875rem;box-sizing:border-box;list-style:none;font-weight:500;padding-top:16px;padding-bottom:16px;}.mud-combobox-subheader-secondary-background{background-color:var(--mud-palette-background-gray);}.mud-combobox-subheader-gutters{padding-left:16px;padding-right:16px;}.mud-combobox-subheader-inset{padding-left:72px;padding-inline-start:72px;padding-inline-end:unset;}.mud-combobox-subheader-sticky{top:-8px;z-index:1;position:sticky;}.mud-combobox-subheader-sticky.mud-combobox-subheader-sticky-dense{top:0;}.mud-combobox-item-hilight{background-color:var(--mud-palette-background-gray);}.mud-combobox-item-hilight{background-color:var(--mud-palette-lines-default) !important;}.mud-combobox-item-bordered{border-left:4px solid var(--mud-palette-lines-default);padding-inline-start:12px;}.mud-combobox-item-bordered-primary{border-left:4px solid var(--mud-palette-primary);padding-inline-start:12px;}.mud-combobox-item-bordered-secondary{border-left:4px solid var(--mud-palette-secondary);padding-inline-start:12px;}.mud-combobox-item-bordered-tertiary{border-left:4px solid var(--mud-palette-tertiary);padding-inline-start:12px;}.mud-combobox-item-bordered-info{border-left:4px solid var(--mud-palette-info);padding-inline-start:12px;}.mud-combobox-item-bordered-success{border-left:4px solid var(--mud-palette-success);padding-inline-start:12px;}.mud-combobox-item-bordered-warning{border-left:4px solid var(--mud-palette-warning);padding-inline-start:12px;}.mud-combobox-item-bordered-error{border-left:4px solid var(--mud-palette-error);padding-inline-start:12px;}.mud-combobox-item-bordered-dark{border-left:4px solid var(--mud-palette-dark);padding-inline-start:12px;}.mud-combobox-item-nested-background{background-color:var(--mud-palette-background-gray);}.mud-combobox-item-avatar{min-width:56px;flex-shrink:0;}.mud-combobox-highlighter{background-color:transparent;font-weight:bold;text-decoration:underline;}.mud-gallery-selected-toolbox{left:0;right:0;height:56px;width:100%;background-color:rgba(0,0,0,.6);}.mud-gallery-selected-toolbox.gallery-toolbox-top{top:0;}.mud-gallery-selected-toolbox.gallery-toolbox-bottom{bottom:0;}.mud-page{display:grid;box-sizing:border-box;width:100%;}.mud-page.mud-page-height-full{min-height:100vh;}.mud-page.mud-page-height-full-without-appbar{min-height:calc(100vh - var(--mud-appbar-height));}.mud-page.mud-page-column-2{grid-template-columns:repeat(2,50%);}.mud-page.mud-page-column-3{grid-template-columns:repeat(3,33.33333%);}.mud-page.mud-page-column-4{grid-template-columns:repeat(4,25%);}.mud-page.mud-page-column-5{grid-template-columns:repeat(5,20%);}.mud-page.mud-page-column-6{grid-template-columns:repeat(6,16.66667%);}.mud-page.mud-page-column-7{grid-template-columns:repeat(7,14.28571%);}.mud-page.mud-page-column-8{grid-template-columns:repeat(8,12.5%);}.mud-page.mud-page-column-9{grid-template-columns:repeat(9,11.11111%);}.mud-page.mud-page-column-Line truncated
|
||||
Vendored
Executable
+1
@@ -0,0 +1 @@
|
||||
function auto_size(n){n.style.height="5px";n.style.height=n.scrollHeight+4+"px"}function getcss(n,t){const i=document.querySelector(n);return i.style.getPropertyValue(t)}function setcss(n,t,i){const r=document.querySelectorAll(n);for(let n=0;n<r.length;n++)r[n].style.setProperty(t,i)}class MudScrollManagerExtended{scrollToMiddle(n,t){let i=document.getElementById(n),r=document.getElementById(t);i.scrollTop=r.offsetTop-i.offsetHeight+i.offsetHeight/2+r.offsetHeight/2}}window.mudScrollManagerExtended=new MudScrollManagerExtended;window.mudTeleport={teleport:(n,t)=>{const i=document.querySelector(t);return i?(i.appendChild(n),"ok"):"not found"},removeFromDOM:n=>{n&&n.__internalId!==null&&n.remove()}};class MudSignaturePadManager{constructor(){this.pads=[]}addPad(n,t,i){const r=new MudSignaturePad(n,t,i);r.init();this.pads.push(r)}togglePadEraser(n){const t=this.getPad(n);t&&t.toggleEraser()}disposePad(n){const t=this.getPad(n);t&&t.dispose()}clearPad(n){const t=this.getPad(n);t&&t.clear(!0)}downloadPadImage(n){const t=this.getPad(n);t&&t.download()}getBase64(n){const t=this.getPad(n);if(t)return t.getBase64()}updatePadOptions(n,t){const i=this.getPad(n);i&&i.setOptions(t)}updatePadImage(n,t){const i=this.getPad(n);if(i){if(t.startsWith("data:image/png;base64,")){i.updateImage(t);return}i.updateImage(`data:image/png;base64,${t}`)}}getPad(n){const t=this.pads.findIndex(t=>t.canvas.id===n.id);return t>=0?this.pads[t]:null}}class MudSignaturePad{constructor(n,t,i){this.canvas=t;this.options=i;this.isMouseDown=!1;this.isErasing=!1;this.memCanvas=document.createElement("canvas");this.points=[];this.dotnetRef=n}get ctx(){return this.canvas.getContext("2d")}get memCtx(){return this.memCanvas.getContext("2d")}getBase64(){return this.canvas.toDataURL()}init(){this.setCanvasSize();this.setOptions(this.options);this.canvas.addEventListener("mousedown",n=>this.startDrawing(n));this.canvas.addEventListener("mousemove",n=>this.drawLine(n));this.canvas.addEventListener("mouseup",()=>this.stopDrawing());this.canvas.addEventListener("mouseout",()=>this.stopDrawing());this.canvas.addEventListener("touchstart",n=>this.startDrawing(n));this.canvas.addEventListener("touchend",()=>this.stopDrawing());this.canvas.addEventListener("touchmove",n=>this.drawLine(n));this.setPencilCursor()}download(){const n=document.createElement("a");n.download="signature.png";n.href=this.canvas.toDataURL();n.click();n.remove()}updateImage(n){this.clear(!0);const t=new Image,i=this.ctx,r=this.memCtx;t.onload=function(){i.drawImage(t,0,0);r.drawImage(t,0,0);t.remove()};t.src=n}setCanvasSize(){const t=this.canvas.parentElement,n=t.getBoundingClientRect();this.canvas.width=n.width;this.canvas.height=n.height;this.memCanvas.height=n.height;this.memCanvas.width=n.width}dispose(){this.canvas.removeEventListener("mousedown");this.canvas.removeEventListener("mousemove");this.canvas.removeEventListener("mouseup");this.canvas.removeEventListener("mouseout");this.canvas.removeEventListener("touchstart");this.canvas.removeEventListener("touchend");this.canvas.removeEventListener("touchmove")}clear(n){n===!0&&this.memCtx.clearRect(0,0,this.canvas.width,this.canvas.height);this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height)}stopDrawing(){this.isMouseDown=!1;this.memCtx.clearRect(0,0,this.memCanvas.width,this.memCanvas.height);this.memCtx.drawImage(this.canvas,0,0);this.points=[]}startDrawing(n){this.isMouseDown=!0;this.points.push({x:n.offsetX,y:n.offsetY})}setOptions(n){this.ctx.lineWidth=n.lineWidth;this.ctx.lineJoin=n.lineJoin;this.ctx.lineCap=n.lineCap;this.ctx.strokeStyle=n.strokeStyle;this.options=n}toggleEraser(){if(this.isErasing=!this.isErasing,this.isErasing){this.setEraserCursor();return}this.setPencilCursor()}setPencilCursor(){this.canvas.setAttribute("style","cursor:url('_content/CodeBeam.MudBlazor.Extensions/pencil.cur'), auto;")}setEraserCursor(){this.canvas.setAttribute("style","cursor:url('_content/CodeBeam.MudBlazor.Extensions/eraser.cur'), auto;")}drawLine(n){this.isMouseDown&&(this.isErasing===!1?(this.clear(),this.ctx.drawImage(this.memCanvas,0,0),this.points.push({x:n.offsetX,y:n.offsetY}),this.drawPoints(this.ctx,this.points)):this.ctx.clearRect(n.offsetX,n.offsetY,23,23))}drawPoints(n,t){if(!(t.length<6)){if(t.length<6){const i=t[0];n.beginPath();n.arc(i.x,i.y,n.lineWidth/2,0,Math.PI*2,!0);n.closePath();n.fill();this.pushUpdateToBlazorComponent();return}n.beginPath();n.moveTo(t[0].x,t[0].y);let i;for(let r=1;r<t.length-2;r++){const u=(t[r].x+t[r+1].x)/2,f=(t[r].y+t[r+1].y)/2;n.quadraticCurveTo(t[r].x,t[r].y,u,f);i=r}n.quadraticCurveTo(t[i].x,t[i].y,t[i+1].x,t[i+1].y);n.stroke();this.pushUpdateToBlazorComponent()}}pushUpdateToBlazorComponent(){this.dotnetRef.invokeMethodAsync("SignatureDataChangedAsync")}}window.mudSignaturePad=new MudSignaturePadManager;
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 4.5 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 326 B |
+8
-8
@@ -1,9 +1,9 @@
|
||||
0.9.18
|
||||
2024-11-09 21:10:03 UTC
|
||||
193
|
||||
8.0.110 (commit 87a66bb3d1)
|
||||
8.0.10 (commit 81cabf2857)
|
||||
1.82.0 (commit f6e511eec)
|
||||
7.14.0
|
||||
0.9.22
|
||||
2024-12-04 10:58:01 UTC
|
||||
197
|
||||
8.0.111 (commit f3835a7fb4)
|
||||
8.0.11 (commit 9cb3b725e3)
|
||||
1.83.0 (commit 90b35a623)
|
||||
7.15.0
|
||||
1.8.1
|
||||
826d15729b5, release
|
||||
d437d086407, release
|
||||
Generated
+2
-2
@@ -1,6 +1,6 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
@@ -2174,7 +2174,7 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "mindwork-ai-studio"
|
||||
version = "0.9.18"
|
||||
version = "0.9.22"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"arboard",
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mindwork-ai-studio"
|
||||
version = "0.9.18"
|
||||
version = "0.9.22"
|
||||
edition = "2021"
|
||||
description = "MindWork AI Studio"
|
||||
authors = ["Thorsten Sommer"]
|
||||
@@ -9,7 +9,7 @@ authors = ["Thorsten Sommer"]
|
||||
tauri-build = { version = "1.5", features = [] }
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "1.8.1", features = [ "http-all", "updater", "shell-sidecar", "shell-open"] }
|
||||
tauri = { version = "1.8", features = [ "http-all", "updater", "shell-sidecar", "shell-open"] }
|
||||
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
@@ -29,7 +29,7 @@ cbc = "0.1.2"
|
||||
pbkdf2 = "0.12.2"
|
||||
hmac = "0.12.1"
|
||||
sha2 = "0.10.8"
|
||||
rcgen = { version = "0.13.1", features = ["pem"] }
|
||||
rcgen = { version = "0.13", features = ["pem"] }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
# See issue https://github.com/tauri-apps/tauri/issues/4470
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
},
|
||||
"package": {
|
||||
"productName": "MindWork AI Studio",
|
||||
"version": "0.9.18"
|
||||
"version": "0.9.22"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
|
||||
Reference in new issue
Block a user