mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-19 21:58:00 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99dac520cb | ||
|
|
e21e30ce8c | ||
|
|
5a077e1bdb | ||
|
|
8dd5b3bab8 | ||
|
|
03431b85b4 | ||
|
|
9699754ec7 | ||
|
|
64656d3605 | ||
|
|
9bd79bd3a0 | ||
|
|
bfc9f2ea1d | ||
|
|
96e6372fcd | ||
|
|
f01cf498e2 | ||
|
|
77d427610b | ||
|
|
7c59aa11fe | ||
|
|
98810ce884 | ||
|
|
657fda4961 | ||
|
|
f4780939fc | ||
|
|
954cf44939 | ||
|
|
e973b51f75 | ||
|
|
c9474c55b7 | ||
|
|
c0b7016c3a | ||
|
|
78315195cb | ||
|
|
6040e88dad | ||
|
|
ad3d8cbb3a | ||
|
|
c836bd7f33 | ||
|
|
9d71978feb | ||
|
|
940459ca46 | ||
|
|
09d67bc1c2 | ||
|
|
1681405fb9 | ||
|
|
be408a6aee | ||
|
|
cf975f2a6c | ||
|
|
1df1d3f328 | ||
|
|
ed14525390 | ||
|
|
14caf550ec | ||
|
|
f50e041640 | ||
|
|
ebe00812e2 | ||
|
|
ae72c50236 | ||
|
|
48874d617a | ||
|
|
63be312bb4 | ||
|
|
06f66fdab2 | ||
|
|
b9a65d9ca0 | ||
|
|
e4b1ea32fb | ||
|
|
e9975a5fbc | ||
|
|
2ef4d9d53f | ||
|
|
33a2728644 |
No files matched your search
@@ -1,4 +1,5 @@
|
||||
# MindWork AI Studio
|
||||
Are you new here? [Read here](#what-is-ai-studio) what AI Studio is.
|
||||
|
||||
## News
|
||||
Things we are currently working on:
|
||||
@@ -11,16 +12,16 @@ Things we are currently working on:
|
||||
- [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))~~
|
||||
- [x] ~~App: Implement an [ERI](https://github.com/MindWorkAI/ERI) server coding assistant (PR [#231](https://github.com/MindWorkAI/AI-Studio/pull/231))~~
|
||||
- [ ] App: Management of data sources (local & external data via [ERI](https://github.com/MindWorkAI/ERI))
|
||||
- [x] ~~App: Management of data sources (local & external data via [ERI](https://github.com/MindWorkAI/ERI)) (PR [#259](https://github.com/MindWorkAI/AI-Studio/pull/259), [#273](https://github.com/MindWorkAI/AI-Studio/pull/273))~~
|
||||
- [ ] Runtime: Extract data from txt / md / pdf / docx / xlsx files
|
||||
- [ ] (*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: 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
|
||||
- [x] ~~App: Define a common retrieval context interface for the integration of RAG processes in chats (PR [#281](https://github.com/MindWorkAI/AI-Studio/pull/281), [#284](https://github.com/MindWorkAI/AI-Studio/pull/284), [#286](https://github.com/MindWorkAI/AI-Studio/pull/286), [#287](https://github.com/MindWorkAI/AI-Studio/pull/287))~~
|
||||
- [x] ~~App: Define a common augmentation interface for the integration of RAG processes in chats (PR [#288](https://github.com/MindWorkAI/AI-Studio/pull/288), [#289](https://github.com/MindWorkAI/AI-Studio/pull/289))~~
|
||||
- [x] ~~App: Integrate data sources in chats (PR [#282](https://github.com/MindWorkAI/AI-Studio/pull/282))~~
|
||||
|
||||
|
||||
- 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)~~.
|
||||
@@ -35,6 +36,7 @@ Other News:
|
||||
|
||||
Features we have recently released:
|
||||
|
||||
- v0.9.25: Added [xAI](https://x.ai/) as a new provider. xAI provides their Grok models for generating content.
|
||||
- v0.9.23: Added support for OpenAI `o` models (`o1`, `o1-mini`, `o3`, etc.); added also an [ERI](https://github.com/MindWorkAI/ERI) server coding assistant as a preview feature behind the RAG feature flag. Your own ERI server can be used to gain access to, e.g., your enterprise data from within AI Studio.
|
||||
- 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.
|
||||
@@ -42,21 +44,23 @@ Features we have recently released:
|
||||
- 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.
|
||||
|
||||
## What is AI Studio?
|
||||
|
||||

|
||||

|
||||
|
||||
MindWork AI Studio is a desktop application available for macOS, Windows, and Linux. It provides a unified chat interface for Large Language Models (LLMs). You bring your own API key for the respective LLM provider to use the models. The API keys are securely stored by the operating system.
|
||||
MindWork AI Studio is a free desktop app for macOS, Windows, and Linux. It provides a unified user interface for interaction with Large Language Models (LLM). AI Studio also offers so-called assistants, where prompting is not necessary. You can think of AI Studio like an email program: you bring your own API key for the LLM of your choice and can then use these AI systems with AI Studio. Whether you want to use Google Gemini, OpenAI o1, or even your own local AI models.
|
||||
|
||||
**Ready to get started 🤩?** [Download the appropriate setup for your operating system here](documentation/Setup.md).
|
||||
|
||||
**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), 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/).
|
||||
- **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, o1, etc.), Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), 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/).
|
||||
- **Assistants**: You just want to quickly translate a text? AI Studio has so-called assistants for such and other tasks. No prompting is necessary when working with these assistants.
|
||||
- **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.
|
||||
- **Privacy**: You can control which providers receive your data using the provider confidence settings. For example, you can set different protection levels for writing emails compared to general chats, etc. Additionally, most providers guarantee that they won't use your data to train new AI systems.
|
||||
- **Flexibility**: Choose the provider and model best suited for your current task.
|
||||
- **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.
|
||||
|
||||
@@ -78,12 +82,15 @@ Your support, whether big or small, keeps the wheels turning and is deeply appre
|
||||
|
||||
## Planned Features
|
||||
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 Retrieval Interface](https://github.com/MindWorkAI/ERI), ERI 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.
|
||||
- **Integrating your data**: You'll be able to integrate your data into AI Studio, like your PDF or Office files, or your Markdown notes.
|
||||
- **Integration of enterprise data:** It will soon be possible to integrate data from the corporate network using a specified interface ([External Retrieval Interface](https://github.com/MindWorkAI/ERI), ERI for short). This will likely require development work by the organization in question.
|
||||
- **Useful assistants:** We'll develop more assistants for everyday tasks.
|
||||
- **Writing mode:** We're integrating a writing mode to help you create extensive works, like comprehensive project proposals, tenders, or your next fantasy novel.
|
||||
- **Specific requirements:** Want an assistant that suits your specific needs? We aim to offer a plugin architecture so organizations and enthusiasts can implement such ideas.
|
||||
- **Voice control:** You'll interact with the AI systems using your voice. 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.
|
||||
- **Content creation:** There will be an interface for AI Studio to create content in other apps. You could, for example, create blog posts directly on the target platform or add entries to an internal knowledge management tool. This requires development work by the tool developers.
|
||||
- **Email monitoring:** You can connect your email inboxes with AI Studio. The AI will read your emails and notify you of important events. You'll also be able to access knowledge from your emails in your chats.
|
||||
- **Browser usage:** We're working on offering AI Studio features in your browser via a plugin, allowing, e.g., for spell-checking or text rewriting directly in the browser.
|
||||
|
||||
Stay tuned for more updates and enhancements to make MindWork AI Studio even more powerful and versatile 🤩.
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MindWork AI Studio", "MindWork AI Studio\MindWork AI Studio.csproj", "{059FDFCC-7D0B-474E-9F20-B9C437DF1CDD}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceCodeRules", "SourceCodeRules\SourceCodeRules\SourceCodeRules.csproj", "{0976C1CB-D499-4C86-8ADA-B7A7A4DE0BF8}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -12,5 +14,11 @@ Global
|
||||
{059FDFCC-7D0B-474E-9F20-B9C437DF1CDD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{059FDFCC-7D0B-474E-9F20-B9C437DF1CDD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{059FDFCC-7D0B-474E-9F20-B9C437DF1CDD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{0976C1CB-D499-4C86-8ADA-B7A7A4DE0BF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0976C1CB-D499-4C86-8ADA-B7A7A4DE0BF8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0976C1CB-D499-4C86-8ADA-B7A7A4DE0BF8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0976C1CB-D499-4C86-8ADA-B7A7A4DE0BF8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -5,6 +5,10 @@
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LLM/@EntryIndexedValue">LLM</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LM/@EntryIndexedValue">LM</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MSG/@EntryIndexedValue">MSG</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RAG/@EntryIndexedValue">RAG</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UI/@EntryIndexedValue">UI</s:String>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=agentic/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=groq/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=mwais/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=ollama/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=tauri_0027s/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
@@ -1,19 +1,37 @@
|
||||
using System.Text.Json;
|
||||
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
|
||||
namespace AIStudio.Agents;
|
||||
|
||||
public abstract class AgentBase(ILogger<AgentBase> logger, SettingsManager settingsManager, ThreadSafeRandom rng) : IAgent
|
||||
public abstract class AgentBase(ILogger<AgentBase> logger, SettingsManager settingsManager, DataSourceService dataSourceService, ThreadSafeRandom rng) : IAgent
|
||||
{
|
||||
protected static readonly ContentBlock EMPTY_BLOCK = new()
|
||||
{
|
||||
Content = null,
|
||||
ContentType = ContentType.NONE,
|
||||
Role = ChatRole.AGENT,
|
||||
Time = DateTimeOffset.UtcNow,
|
||||
};
|
||||
|
||||
protected static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||
};
|
||||
|
||||
protected DataSourceService DataSourceService { get; init; } = dataSourceService;
|
||||
|
||||
protected SettingsManager SettingsManager { get; init; } = settingsManager;
|
||||
|
||||
protected ThreadSafeRandom RNG { get; init; } = rng;
|
||||
|
||||
protected ILogger<AgentBase> Logger { get; init; } = logger;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Represents the type or category of this agent.
|
||||
/// </summary>
|
||||
@@ -60,24 +78,30 @@ public abstract class AgentBase(ILogger<AgentBase> logger, SettingsManager setti
|
||||
Blocks = [],
|
||||
};
|
||||
|
||||
protected DateTimeOffset AddUserRequest(ChatThread thread, string request)
|
||||
protected UserRequest AddUserRequest(ChatThread thread, string request)
|
||||
{
|
||||
var time = DateTimeOffset.Now;
|
||||
var lastUserPrompt = new ContentText
|
||||
{
|
||||
Text = request,
|
||||
};
|
||||
|
||||
thread.Blocks.Add(new ContentBlock
|
||||
{
|
||||
Time = time,
|
||||
ContentType = ContentType.TEXT,
|
||||
Role = ChatRole.USER,
|
||||
Content = new ContentText
|
||||
{
|
||||
Text = request,
|
||||
},
|
||||
Content = lastUserPrompt,
|
||||
});
|
||||
|
||||
return time;
|
||||
|
||||
return new()
|
||||
{
|
||||
Time = time,
|
||||
UserPrompt = lastUserPrompt,
|
||||
};
|
||||
}
|
||||
|
||||
protected async Task AddAIResponseAsync(ChatThread thread, DateTimeOffset time)
|
||||
protected async Task AddAIResponseAsync(ChatThread thread, IContent lastUserPrompt, DateTimeOffset time)
|
||||
{
|
||||
if(this.ProviderSettings is null)
|
||||
return;
|
||||
@@ -103,6 +127,6 @@ public abstract class AgentBase(ILogger<AgentBase> logger, SettingsManager setti
|
||||
// Use the selected provider to get the AI response.
|
||||
// By awaiting this line, we wait for the entire
|
||||
// content to be streamed.
|
||||
await aiText.CreateFromProviderAsync(providerSettings.CreateProvider(this.Logger), this.SettingsManager, providerSettings.Model, thread);
|
||||
await aiText.CreateFromProviderAsync(providerSettings.CreateProvider(this.Logger), providerSettings.Model, lastUserPrompt, thread);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,398 @@
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Settings.DataModel;
|
||||
using AIStudio.Tools.ERIClient;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
namespace AIStudio.Agents;
|
||||
|
||||
public sealed class AgentDataSourceSelection (ILogger<AgentDataSourceSelection> logger, ILogger<AgentBase> baseLogger, SettingsManager settingsManager, DataSourceService dataSourceService, ThreadSafeRandom rng) : AgentBase(baseLogger, settingsManager, dataSourceService, rng)
|
||||
{
|
||||
private readonly List<ContentBlock> answers = new();
|
||||
|
||||
#region Overrides of AgentBase
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Type Type => Type.SYSTEM;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string Id => "Data Source Selection";
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override string JobDescription =>
|
||||
"""
|
||||
You receive a system and a user prompt, as well as a list of possible data sources as input.
|
||||
Your task is to select the appropriate data sources for the given task. You may choose none,
|
||||
one, or multiple sources, depending on what best fits the system and user prompt. You need
|
||||
to estimate and assess which source, based on its description, might be helpful in
|
||||
processing the prompts.
|
||||
|
||||
Your response is a JSON list in the following format:
|
||||
|
||||
```
|
||||
[
|
||||
{"id": "The data source ID", "reason": "Why did you choose this source?", "confidence": 0.87},
|
||||
{"id": "The data source ID", "reason": "Why did you choose this source?", "confidence": 0.54}
|
||||
]
|
||||
```
|
||||
|
||||
You express your confidence as a floating-point number between 0.0 (maximum uncertainty) and
|
||||
1.0 (you are absolutely certain that this source is needed).
|
||||
|
||||
The JSON schema is:
|
||||
|
||||
```
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"reason": {
|
||||
"type": "string"
|
||||
},
|
||||
"confidence": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"reason",
|
||||
"confidence"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
When no data source is needed, you return an empty JSON list `[]`. You do not ask any
|
||||
follow-up questions. You do not address the user. Your response consists solely of
|
||||
the JSON list.
|
||||
""";
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override string SystemPrompt(string availableDataSources) => $"""
|
||||
{this.JobDescription}
|
||||
|
||||
{availableDataSources}
|
||||
""";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Settings.Provider? ProviderSettings { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The data source selection agent does not work with context. Use
|
||||
/// the process input method instead.
|
||||
/// </summary>
|
||||
/// <returns>The chat thread without any changes.</returns>
|
||||
public override Task<ChatThread> ProcessContext(ChatThread chatThread, IDictionary<string, string> additionalData) => Task.FromResult(chatThread);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<ContentBlock> ProcessInput(ContentBlock input, IDictionary<string, string> additionalData)
|
||||
{
|
||||
if (input.Content is not ContentText text)
|
||||
return EMPTY_BLOCK;
|
||||
|
||||
if(text.InitialRemoteWait || text.IsStreaming)
|
||||
return EMPTY_BLOCK;
|
||||
|
||||
if(string.IsNullOrWhiteSpace(text.Text))
|
||||
return EMPTY_BLOCK;
|
||||
|
||||
if(!additionalData.TryGetValue("availableDataSources", out var availableDataSources) || string.IsNullOrWhiteSpace(availableDataSources))
|
||||
return EMPTY_BLOCK;
|
||||
|
||||
var thread = this.CreateChatThread(this.SystemPrompt(availableDataSources));
|
||||
var userRequest = this.AddUserRequest(thread, text.Text);
|
||||
await this.AddAIResponseAsync(thread, userRequest.UserPrompt, userRequest.Time);
|
||||
|
||||
var answer = thread.Blocks[^1];
|
||||
|
||||
this.answers.Add(answer);
|
||||
return answer;
|
||||
}
|
||||
|
||||
// <inheritdoc />
|
||||
public override Task<bool> MadeDecision(ContentBlock input) => Task.FromResult(true);
|
||||
|
||||
// <inheritdoc />
|
||||
public override IReadOnlyCollection<ContentBlock> GetContext() => [];
|
||||
|
||||
// <inheritdoc />
|
||||
public override IReadOnlyCollection<ContentBlock> GetAnswers() => this.answers;
|
||||
|
||||
#endregion
|
||||
|
||||
public async Task<List<SelectedDataSource>> PerformSelectionAsync(IProvider provider, IContent lastPrompt, ChatThread chatThread, AllowedSelectedDataSources dataSources, CancellationToken token = default)
|
||||
{
|
||||
logger.LogInformation("The AI should select the appropriate data sources.");
|
||||
|
||||
//
|
||||
// 1. Which LLM provider should the agent use?
|
||||
//
|
||||
|
||||
// We start with the provider currently selected by the user:
|
||||
var agentProvider = this.SettingsManager.GetPreselectedProvider(Tools.Components.AGENT_DATA_SOURCE_SELECTION, provider.Id, true);
|
||||
|
||||
// Assign the provider settings to the agent:
|
||||
logger.LogInformation($"The agent for the data source selection uses the provider '{agentProvider.InstanceName}' ({agentProvider.UsedLLMProvider.ToName()}, confidence={agentProvider.UsedLLMProvider.GetConfidence(this.SettingsManager).Level.GetName()}).");
|
||||
this.ProviderSettings = agentProvider;
|
||||
|
||||
//
|
||||
// 2. Prepare the current system and user prompts as input for the agent:
|
||||
//
|
||||
var lastPromptContent = lastPrompt switch
|
||||
{
|
||||
ContentText text => text.Text,
|
||||
|
||||
// Image prompts may be empty, e.g., when the image is too large:
|
||||
ContentImage image => await image.AsBase64(token),
|
||||
|
||||
// Other content types are not supported yet:
|
||||
_ => string.Empty,
|
||||
};
|
||||
|
||||
if (string.IsNullOrWhiteSpace(lastPromptContent))
|
||||
{
|
||||
logger.LogWarning("The last prompt is empty. The AI cannot select data sources.");
|
||||
return [];
|
||||
}
|
||||
|
||||
//
|
||||
// 3. Prepare the allowed data sources as input for the agent:
|
||||
//
|
||||
var additionalData = new Dictionary<string, string>();
|
||||
logger.LogInformation("Preparing the list of allowed data sources for the agent to choose from.");
|
||||
|
||||
// Notice: We do not dispose the Rust service here. The Rust service is a singleton
|
||||
// and will be disposed when the application shuts down:
|
||||
var rustService = Program.SERVICE_PROVIDER.GetService<RustService>()!;
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("The following data sources are available for selection:");
|
||||
foreach (var ds in dataSources.AllowedDataSources)
|
||||
{
|
||||
switch (ds)
|
||||
{
|
||||
case DataSourceLocalDirectory localDirectory:
|
||||
sb.AppendLine($"- Id={ds.Id}, name='{localDirectory.Name}', type=local directory, path='{localDirectory.Path}'");
|
||||
break;
|
||||
|
||||
case DataSourceLocalFile localFile:
|
||||
sb.AppendLine($"- Id={ds.Id}, name='{localFile.Name}', type=local file, path='{localFile.FilePath}'");
|
||||
break;
|
||||
|
||||
case IERIDataSource eriDataSource:
|
||||
var eriServerDescription = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
//
|
||||
// Call the ERI server to get the server description:
|
||||
//
|
||||
using var eriClient = ERIClientFactory.Get(eriDataSource.Version, eriDataSource)!;
|
||||
var authResponse = await eriClient.AuthenticateAsync(eriDataSource, rustService, token);
|
||||
if (authResponse.Successful)
|
||||
{
|
||||
var serverDescriptionResponse = await eriClient.GetDataSourceInfoAsync(token);
|
||||
if (serverDescriptionResponse.Successful)
|
||||
{
|
||||
eriServerDescription = serverDescriptionResponse.Data.Description;
|
||||
|
||||
// Remove all line breaks from the description:
|
||||
eriServerDescription = eriServerDescription.Replace("\n", " ").Replace("\r", " ");
|
||||
}
|
||||
else
|
||||
logger.LogWarning($"Was not able to retrieve the server description from the ERI data source '{eriDataSource.Name}'. Message: {serverDescriptionResponse.Message}");
|
||||
}
|
||||
else
|
||||
logger.LogWarning($"Was not able to authenticate with the ERI data source '{eriDataSource.Name}'. Message: {authResponse.Message}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogWarning($"The ERI data source '{eriDataSource.Name}' is not available. Thus, we cannot retrieve the server description. Error: {e.Message}");
|
||||
}
|
||||
|
||||
//
|
||||
// Append the ERI data source to the list. Use the server description if available:
|
||||
//
|
||||
if (string.IsNullOrWhiteSpace(eriServerDescription))
|
||||
sb.AppendLine($"- Id={ds.Id}, name='{eriDataSource.Name}', type=external data source");
|
||||
else
|
||||
sb.AppendLine($"- Id={ds.Id}, name='{eriDataSource.Name}', type=external data source, description='{eriServerDescription}'");
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
logger.LogInformation("Prepared the list of allowed data sources for the agent.");
|
||||
additionalData.Add("availableDataSources", sb.ToString());
|
||||
|
||||
//
|
||||
// 4. Let the agent select the data sources:
|
||||
//
|
||||
var prompt = $"""
|
||||
The system prompt is:
|
||||
|
||||
```
|
||||
{chatThread.SystemPrompt}
|
||||
```
|
||||
|
||||
The user prompt is:
|
||||
|
||||
```
|
||||
{lastPromptContent}
|
||||
```
|
||||
""";
|
||||
|
||||
// Call the agent:
|
||||
var aiResponse = await this.ProcessInput(new ContentBlock
|
||||
{
|
||||
Time = DateTimeOffset.UtcNow,
|
||||
ContentType = ContentType.TEXT,
|
||||
Role = ChatRole.USER,
|
||||
Content = new ContentText
|
||||
{
|
||||
Text = prompt,
|
||||
},
|
||||
}, additionalData);
|
||||
|
||||
if(aiResponse.Content is null)
|
||||
{
|
||||
logger.LogWarning("The agent did not return a response.");
|
||||
return [];
|
||||
}
|
||||
|
||||
switch (aiResponse)
|
||||
{
|
||||
|
||||
//
|
||||
// 5. Parse the agent response:
|
||||
//
|
||||
case { ContentType: ContentType.TEXT, Content: ContentText textContent }:
|
||||
{
|
||||
//
|
||||
// What we expect is a JSON list of SelectedDataSource objects:
|
||||
//
|
||||
var selectedDataSourcesJson = textContent.Text;
|
||||
|
||||
//
|
||||
// We know how bad LLM may be in generating JSON without surrounding text.
|
||||
// Thus, we expect the worst and try to extract the JSON list from the text:
|
||||
//
|
||||
var json = ExtractJson(selectedDataSourcesJson);
|
||||
|
||||
try
|
||||
{
|
||||
var aiSelectedDataSources = JsonSerializer.Deserialize<List<SelectedDataSource>>(json, JSON_SERIALIZER_OPTIONS);
|
||||
return aiSelectedDataSources ?? [];
|
||||
}
|
||||
catch
|
||||
{
|
||||
logger.LogWarning("The agent answered with an invalid or unexpected JSON format.");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
case { ContentType: ContentType.TEXT }:
|
||||
logger.LogWarning("The agent answered with an unexpected inner content type.");
|
||||
return [];
|
||||
|
||||
case { ContentType: ContentType.NONE }:
|
||||
logger.LogWarning("The agent did not return a response.");
|
||||
return [];
|
||||
|
||||
default:
|
||||
logger.LogWarning($"The agent answered with an unexpected content type '{aiResponse.ContentType}'.");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the JSON list from the given text. The text may contain additional
|
||||
/// information around the JSON list. The method tries to extract the JSON list
|
||||
/// from the text.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Algorithm: The method searches for the first line that contains only a '[' character.
|
||||
/// Then, it searches for the first line that contains only a ']' character. The method
|
||||
/// returns the text between these two lines (including the brackets). When the method
|
||||
/// cannot find the JSON list, it returns an empty string.
|
||||
/// <br/><br/>
|
||||
/// This overload is using strings instead of spans. We can use this overload in any
|
||||
/// async method. Thus, it is a wrapper around the span-based method. Yes, we are losing
|
||||
/// the memory efficiency of the span-based method, but we still gain the performance
|
||||
/// of the span-based method: the entire search algorithm is span-based.
|
||||
/// </remarks>
|
||||
/// <param name="text">The text that may contain the JSON list.</param>
|
||||
/// <returns>The extracted JSON list.</returns>
|
||||
private static string ExtractJson(string text) => ExtractJson(text.AsSpan()).ToString();
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the JSON list from the given text. The text may contain additional
|
||||
/// information around the JSON list. The method tries to extract the JSON list
|
||||
/// from the text.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Algorithm: The method searches for the first line that contains only a '[' character.
|
||||
/// Then, it searches for the first line that contains only a ']' character. The method
|
||||
/// returns the text between these two lines (including the brackets). When the method
|
||||
/// cannot find the JSON list, it returns an empty string.
|
||||
/// </remarks>
|
||||
/// <param name="text">The text that may contain the JSON list.</param>
|
||||
/// <returns>The extracted JSON list.</returns>
|
||||
private static ReadOnlySpan<char> ExtractJson(ReadOnlySpan<char> text)
|
||||
{
|
||||
var startIndex = -1;
|
||||
var endIndex = -1;
|
||||
var foundStart = false;
|
||||
var foundEnd = false;
|
||||
var lineStart = 0;
|
||||
|
||||
for (var i = 0; i <= text.Length; i++)
|
||||
{
|
||||
// Handle the end of the line or the end of the text:
|
||||
if (i == text.Length || text[i] == '\n')
|
||||
{
|
||||
if (IsCharacterAloneInLine(text, lineStart, i, '[') && !foundStart)
|
||||
{
|
||||
startIndex = lineStart;
|
||||
foundStart = true;
|
||||
}
|
||||
else if (IsCharacterAloneInLine(text, lineStart, i, ']') && foundStart && !foundEnd)
|
||||
{
|
||||
endIndex = i;
|
||||
foundEnd = true;
|
||||
break;
|
||||
}
|
||||
|
||||
lineStart = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundStart && foundEnd)
|
||||
{
|
||||
// Adjust endIndex for slicing, ensuring it's within bounds:
|
||||
return text.Slice(startIndex, Math.Min(text.Length, endIndex + 1) - startIndex);
|
||||
}
|
||||
|
||||
return ReadOnlySpan<char>.Empty;
|
||||
}
|
||||
|
||||
private static bool IsCharacterAloneInLine(ReadOnlySpan<char> text, int lineStart, int lineEnd, char character)
|
||||
{
|
||||
for (var i = lineStart; i < lineEnd; i++)
|
||||
if (!char.IsWhiteSpace(text[i]) && text[i] != character)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
using System.Text.Json;
|
||||
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools.RAG;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
namespace AIStudio.Agents;
|
||||
|
||||
public sealed class AgentRetrievalContextValidation (ILogger<AgentRetrievalContextValidation> logger, ILogger<AgentBase> baseLogger, SettingsManager settingsManager, DataSourceService dataSourceService, ThreadSafeRandom rng) : AgentBase(baseLogger, settingsManager, dataSourceService, rng)
|
||||
{
|
||||
#region Overrides of AgentBase
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Type Type => Type.WORKER;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string Id => "Retrieval Context Validation";
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override string JobDescription =>
|
||||
"""
|
||||
You receive a system and user prompt as well as a retrieval context as input. Your task is to decide whether this
|
||||
retrieval context is helpful in processing the prompts or not. You respond with the decision (true or false),
|
||||
your reasoning, and your confidence in this decision.
|
||||
|
||||
Your response is only one JSON object in the following format:
|
||||
|
||||
```
|
||||
{"decision": true, "reason": "Why did you choose this source?", "confidence": 0.87}
|
||||
```
|
||||
|
||||
You express your confidence as a floating-point number between 0.0 (maximum uncertainty) and
|
||||
1.0 (you are absolutely certain that this retrieval context is needed).
|
||||
|
||||
The JSON schema is:
|
||||
|
||||
```
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"decision": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"reason": {
|
||||
"type": "string"
|
||||
},
|
||||
"confidence": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"decision",
|
||||
"reason",
|
||||
"confidence"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
You do not ask any follow-up questions. You do not address the user. Your response consists solely of
|
||||
that one JSON object.
|
||||
""";
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override string SystemPrompt(string retrievalContext) => $"""
|
||||
{this.JobDescription}
|
||||
|
||||
{retrievalContext}
|
||||
""";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Settings.Provider? ProviderSettings { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The retrieval context validation agent does not work with context. Use
|
||||
/// the process input method instead.
|
||||
/// </summary>
|
||||
/// <returns>The chat thread without any changes.</returns>
|
||||
public override Task<ChatThread> ProcessContext(ChatThread chatThread, IDictionary<string, string> additionalData) => Task.FromResult(chatThread);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<ContentBlock> ProcessInput(ContentBlock input, IDictionary<string, string> additionalData)
|
||||
{
|
||||
if (input.Content is not ContentText text)
|
||||
return EMPTY_BLOCK;
|
||||
|
||||
if(text.InitialRemoteWait || text.IsStreaming)
|
||||
return EMPTY_BLOCK;
|
||||
|
||||
if(string.IsNullOrWhiteSpace(text.Text))
|
||||
return EMPTY_BLOCK;
|
||||
|
||||
if(!additionalData.TryGetValue("retrievalContext", out var retrievalContext) || string.IsNullOrWhiteSpace(retrievalContext))
|
||||
return EMPTY_BLOCK;
|
||||
|
||||
var thread = this.CreateChatThread(this.SystemPrompt(retrievalContext));
|
||||
var userRequest = this.AddUserRequest(thread, text.Text);
|
||||
await this.AddAIResponseAsync(thread, userRequest.UserPrompt, userRequest.Time);
|
||||
|
||||
return thread.Blocks[^1];
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<bool> MadeDecision(ContentBlock input) => Task.FromResult(true);
|
||||
|
||||
/// <summary>
|
||||
/// We do not provide any context. This agent will process many retrieval contexts.
|
||||
/// This would block a huge amount of memory.
|
||||
/// </summary>
|
||||
/// <returns>An empty list.</returns>
|
||||
public override IReadOnlyCollection<ContentBlock> GetContext() => [];
|
||||
|
||||
/// <summary>
|
||||
/// We do not provide any answers. This agent will process many retrieval contexts.
|
||||
/// This would block a huge amount of memory.
|
||||
/// </summary>
|
||||
/// <returns>An empty list.</returns>
|
||||
public override IReadOnlyCollection<ContentBlock> GetAnswers() => [];
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Sets the LLM provider for the agent.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When you have to call the validation in parallel for many retrieval contexts,
|
||||
/// you can set the provider once and then call the validation method in parallel.
|
||||
/// </remarks>
|
||||
/// <param name="provider">The current LLM provider. When the user doesn't preselect an agent provider, the agent uses this provider.</param>
|
||||
public void SetLLMProvider(IProvider provider)
|
||||
{
|
||||
// We start with the provider currently selected by the user:
|
||||
var agentProvider = this.SettingsManager.GetPreselectedProvider(Tools.Components.AGENT_RETRIEVAL_CONTEXT_VALIDATION, provider.Id, true);
|
||||
|
||||
// Assign the provider settings to the agent:
|
||||
logger.LogInformation($"The agent for the retrieval context validation uses the provider '{agentProvider.InstanceName}' ({agentProvider.UsedLLMProvider.ToName()}, confidence={agentProvider.UsedLLMProvider.GetConfidence(this.SettingsManager).Level.GetName()}).");
|
||||
this.ProviderSettings = agentProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate all retrieval contexts against the last user and the system prompt.
|
||||
/// </summary>
|
||||
/// <param name="lastPrompt">The last user prompt.</param>
|
||||
/// <param name="chatThread">The chat thread.</param>
|
||||
/// <param name="retrievalContexts">All retrieval contexts to validate.</param>
|
||||
/// <param name="token">The cancellation token.</param>
|
||||
/// <returns>The validation results.</returns>
|
||||
public async Task<IReadOnlyList<RetrievalContextValidationResult>> ValidateRetrievalContextsAsync(IContent lastPrompt, ChatThread chatThread, IReadOnlyList<IRetrievalContext> retrievalContexts, CancellationToken token = default)
|
||||
{
|
||||
// Check if the retrieval context validation is enabled:
|
||||
if (!this.SettingsManager.ConfigurationData.AgentRetrievalContextValidation.EnableRetrievalContextValidation)
|
||||
return [];
|
||||
|
||||
logger.LogInformation($"Validating {retrievalContexts.Count:###,###,###,###} retrieval contexts.");
|
||||
|
||||
// Prepare the list of validation tasks:
|
||||
var validationTasks = new List<Task<RetrievalContextValidationResult>>(retrievalContexts.Count);
|
||||
|
||||
// Read the number of parallel validations:
|
||||
var numParallelValidations = 3;
|
||||
if(this.SettingsManager.ConfigurationData.AgentRetrievalContextValidation.PreselectAgentOptions)
|
||||
numParallelValidations = this.SettingsManager.ConfigurationData.AgentRetrievalContextValidation.NumParallelValidations;
|
||||
|
||||
numParallelValidations = Math.Max(1, numParallelValidations);
|
||||
|
||||
// Use a semaphore to limit the number of parallel validations:
|
||||
using var semaphore = new SemaphoreSlim(numParallelValidations);
|
||||
foreach (var retrievalContext in retrievalContexts)
|
||||
{
|
||||
// Wait for an available slot in the semaphore:
|
||||
await semaphore.WaitAsync(token);
|
||||
|
||||
// Start the next validation task:
|
||||
validationTasks.Add(this.ValidateRetrievalContextAsync(lastPrompt, chatThread, retrievalContext, token, semaphore));
|
||||
}
|
||||
|
||||
// Wait for all validation tasks to complete:
|
||||
return await Task.WhenAll(validationTasks);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the retrieval context against the last user and the system prompt.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Probably, you have a lot of retrieval contexts to validate. In this case, you
|
||||
/// can call this method in parallel for each retrieval context. You might use
|
||||
/// the ValidateRetrievalContextsAsync method to validate all retrieval contexts.
|
||||
/// </remarks>
|
||||
/// <param name="lastPrompt">The last user prompt.</param>
|
||||
/// <param name="chatThread">The chat thread.</param>
|
||||
/// <param name="retrievalContext">The retrieval context to validate.</param>
|
||||
/// <param name="token">The cancellation token.</param>
|
||||
/// <param name="semaphore">The optional semaphore to limit the number of parallel validations.</param>
|
||||
/// <returns>The validation result.</returns>
|
||||
public async Task<RetrievalContextValidationResult> ValidateRetrievalContextAsync(IContent lastPrompt, ChatThread chatThread, IRetrievalContext retrievalContext, CancellationToken token = default, SemaphoreSlim? semaphore = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
//
|
||||
// Check if the validation was canceled. This could happen when the user
|
||||
// canceled the validation process or when the validation process took
|
||||
// too long:
|
||||
//
|
||||
if(token.IsCancellationRequested)
|
||||
return new(false, "The validation was canceled.", 1.0f, retrievalContext);
|
||||
|
||||
//
|
||||
// 1. Prepare the current system and user prompts as input for the agent:
|
||||
//
|
||||
var lastPromptContent = lastPrompt switch
|
||||
{
|
||||
ContentText text => text.Text,
|
||||
|
||||
// Image prompts may be empty, e.g., when the image is too large:
|
||||
ContentImage image => await image.AsBase64(token),
|
||||
|
||||
// Other content types are not supported yet:
|
||||
_ => string.Empty,
|
||||
};
|
||||
|
||||
if (string.IsNullOrWhiteSpace(lastPromptContent))
|
||||
{
|
||||
logger.LogWarning("The last prompt is empty. The AI cannot validate the retrieval context.");
|
||||
return new(false, "The last prompt was empty.", 1.0f, retrievalContext);
|
||||
}
|
||||
|
||||
//
|
||||
// 2. Prepare the retrieval context for the agent:
|
||||
//
|
||||
var additionalData = new Dictionary<string, string>();
|
||||
var markdownRetrievalContext = await retrievalContext.AsMarkdown(token: token);
|
||||
additionalData.Add("retrievalContext", markdownRetrievalContext);
|
||||
|
||||
//
|
||||
// 3. Let the agent validate the retrieval context:
|
||||
//
|
||||
var prompt = $"""
|
||||
The system prompt is:
|
||||
|
||||
```
|
||||
{chatThread.SystemPrompt}
|
||||
```
|
||||
|
||||
The user prompt is:
|
||||
|
||||
```
|
||||
{lastPromptContent}
|
||||
```
|
||||
""";
|
||||
|
||||
// Call the agent:
|
||||
var aiResponse = await this.ProcessInput(new ContentBlock
|
||||
{
|
||||
Time = DateTimeOffset.UtcNow,
|
||||
ContentType = ContentType.TEXT,
|
||||
Role = ChatRole.USER,
|
||||
Content = new ContentText
|
||||
{
|
||||
Text = prompt,
|
||||
},
|
||||
}, additionalData);
|
||||
|
||||
if (aiResponse.Content is null)
|
||||
{
|
||||
logger.LogWarning("The agent did not return a response.");
|
||||
return new(false, "The agent did not return a response.", 1.0f, retrievalContext);
|
||||
}
|
||||
|
||||
switch (aiResponse)
|
||||
{
|
||||
|
||||
//
|
||||
// 4. Parse the agent response:
|
||||
//
|
||||
case { ContentType: ContentType.TEXT, Content: ContentText textContent }:
|
||||
{
|
||||
//
|
||||
// What we expect is one JSON object:
|
||||
//
|
||||
var validationJson = textContent.Text;
|
||||
|
||||
//
|
||||
// We know how bad LLM may be in generating JSON without surrounding text.
|
||||
// Thus, we expect the worst and try to extract the JSON list from the text:
|
||||
//
|
||||
var json = ExtractJson(validationJson);
|
||||
|
||||
try
|
||||
{
|
||||
var result = JsonSerializer.Deserialize<RetrievalContextValidationResult>(json, JSON_SERIALIZER_OPTIONS);
|
||||
return result with { RetrievalContext = retrievalContext };
|
||||
}
|
||||
catch
|
||||
{
|
||||
logger.LogWarning("The agent answered with an invalid or unexpected JSON format.");
|
||||
return new(false, "The agent answered with an invalid or unexpected JSON format.", 1.0f, retrievalContext);
|
||||
}
|
||||
}
|
||||
|
||||
case { ContentType: ContentType.TEXT }:
|
||||
logger.LogWarning("The agent answered with an unexpected inner content type.");
|
||||
return new(false, "The agent answered with an unexpected inner content type.", 1.0f, retrievalContext);
|
||||
|
||||
case { ContentType: ContentType.NONE }:
|
||||
logger.LogWarning("The agent did not return a response.");
|
||||
return new(false, "The agent did not return a response.", 1.0f, retrievalContext);
|
||||
|
||||
default:
|
||||
logger.LogWarning($"The agent answered with an unexpected content type '{aiResponse.ContentType}'.");
|
||||
return new(false, $"The agent answered with an unexpected content type '{aiResponse.ContentType}'.", 1.0f, retrievalContext);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Release the semaphore slot:
|
||||
semaphore?.Release();
|
||||
}
|
||||
}
|
||||
|
||||
// A wrapper around the span version, because we need to call this method from an async context.
|
||||
private static string ExtractJson(string text) => ExtractJson(text.AsSpan()).ToString();
|
||||
|
||||
private static ReadOnlySpan<char> ExtractJson(ReadOnlySpan<char> input)
|
||||
{
|
||||
//
|
||||
// 1. Expect the best case ;-)
|
||||
//
|
||||
if (CheckJsonObjectStart(input))
|
||||
return ExtractJsonPart(input);
|
||||
|
||||
//
|
||||
// 2. Okay, we have some garbage before the
|
||||
// JSON object. We expected that...
|
||||
//
|
||||
for (var index = 0; index < input.Length; index++)
|
||||
{
|
||||
if (input[index] is '{' && CheckJsonObjectStart(input[index..]))
|
||||
return ExtractJsonPart(input[index..]);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
private static bool CheckJsonObjectStart(ReadOnlySpan<char> area)
|
||||
{
|
||||
char[] expectedSymbols = ['{', '"', 'd'];
|
||||
var symbolIndex = 0;
|
||||
|
||||
foreach (var c in area)
|
||||
{
|
||||
if (symbolIndex >= expectedSymbols.Length)
|
||||
return true;
|
||||
|
||||
if (char.IsWhiteSpace(c))
|
||||
continue;
|
||||
|
||||
if (c == expectedSymbols[symbolIndex++])
|
||||
continue;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static ReadOnlySpan<char> ExtractJsonPart(ReadOnlySpan<char> input)
|
||||
{
|
||||
var insideString = false;
|
||||
for (var index = 0; index < input.Length; index++)
|
||||
{
|
||||
if (input[index] is '"')
|
||||
{
|
||||
insideString = !insideString;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (insideString)
|
||||
continue;
|
||||
|
||||
if (input[index] is '}')
|
||||
return input[..++index];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,17 @@
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
namespace AIStudio.Agents;
|
||||
|
||||
public sealed class AgentTextContentCleaner(ILogger<AgentBase> logger, SettingsManager settingsManager, ThreadSafeRandom rng) : AgentBase(logger, settingsManager, rng)
|
||||
public sealed class AgentTextContentCleaner(ILogger<AgentBase> logger, SettingsManager settingsManager, DataSourceService dataSourceService, ThreadSafeRandom rng) : AgentBase(logger, settingsManager, dataSourceService, rng)
|
||||
{
|
||||
private static readonly ContentBlock EMPTY_BLOCK = new()
|
||||
{
|
||||
Content = null,
|
||||
ContentType = ContentType.NONE,
|
||||
Role = ChatRole.AGENT,
|
||||
Time = DateTimeOffset.UtcNow,
|
||||
};
|
||||
|
||||
private readonly List<ContentBlock> context = new();
|
||||
private readonly List<ContentBlock> answers = new();
|
||||
|
||||
#region Overrides of AgentBase
|
||||
|
||||
public override Settings.Provider? ProviderSettings { get; set; }
|
||||
public override AIStudio.Settings.Provider? ProviderSettings { get; set; }
|
||||
|
||||
protected override Type Type => Type.SYSTEM;
|
||||
|
||||
@@ -72,8 +65,8 @@ public sealed class AgentTextContentCleaner(ILogger<AgentBase> logger, SettingsM
|
||||
return EMPTY_BLOCK;
|
||||
|
||||
var thread = this.CreateChatThread(this.SystemPrompt(sourceURL));
|
||||
var time = this.AddUserRequest(thread, text.Text);
|
||||
await this.AddAIResponseAsync(thread, time);
|
||||
var userRequest = this.AddUserRequest(thread, text.Text);
|
||||
await this.AddAIResponseAsync(thread, userRequest.UserPrompt, userRequest.Time);
|
||||
|
||||
var answer = thread.Blocks[^1];
|
||||
this.answers.Add(answer);
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
using AIStudio.Tools.RAG;
|
||||
|
||||
namespace AIStudio.Agents;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the result of a retrieval context validation.
|
||||
/// </summary>
|
||||
/// <param name="Decision">Whether the retrieval context is useful or not.</param>
|
||||
/// <param name="Reason">The reason for the decision.</param>
|
||||
/// <param name="Confidence">The confidence of the decision.</param>
|
||||
/// <param name="RetrievalContext">The retrieval context that was validated.</param>
|
||||
public readonly record struct RetrievalContextValidationResult(bool Decision, string Reason, float Confidence, IRetrievalContext? RetrievalContext) : IConfidence;
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace AIStudio.Agents;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a selected data source, chosen by the agent.
|
||||
/// </summary>
|
||||
/// <param name="Id">The data source ID.</param>
|
||||
/// <param name="Reason">The reason for selecting the data source.</param>
|
||||
/// <param name="Confidence">The confidence of the agent in the selection.</param>
|
||||
public readonly record struct SelectedDataSource(string Id, string Reason, float Confidence) : IConfidence;
|
||||
@@ -0,0 +1,19 @@
|
||||
using AIStudio.Chat;
|
||||
|
||||
namespace AIStudio.Agents;
|
||||
|
||||
/// <summary>
|
||||
/// The created user request.
|
||||
/// </summary>
|
||||
public sealed class UserRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// The time when the request was created.
|
||||
/// </summary>
|
||||
public required DateTimeOffset Time { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The user prompt.
|
||||
/// </summary>
|
||||
public required IContent UserPrompt { get; init; }
|
||||
}
|
||||
@@ -1,131 +1,134 @@
|
||||
@using AIStudio.Chat
|
||||
|
||||
<MudText Typo="Typo.h3" Class="mb-2 mr-3">
|
||||
@(this.Title)
|
||||
</MudText>
|
||||
<div class="inner-scrolling-context">
|
||||
|
||||
<InnerScrolling HeaderHeight="6em">
|
||||
<ChildContent>
|
||||
<MudForm @ref="@(this.form)" @bind-IsValid="@(this.inputIsValid)" @bind-Errors="@(this.inputIssues)" FieldChanged="@this.TriggerFormChange" Class="pr-2">
|
||||
<MudText Typo="Typo.body1" Align="Align.Justify" Class="mb-6">
|
||||
@this.Description
|
||||
</MudText>
|
||||
<MudText Typo="Typo.h3" Class="mb-2 mr-3">
|
||||
@(this.Title)
|
||||
</MudText>
|
||||
|
||||
@if (this.Body is not null)
|
||||
{
|
||||
<CascadingValue Value="@this">
|
||||
@this.Body
|
||||
</CascadingValue>
|
||||
|
||||
<MudButton Disabled="@this.SubmitDisabled" Variant="Variant.Filled" Class="mb-3" OnClick="() => this.SubmitAction()" Style="@this.SubmitButtonStyle">
|
||||
@this.SubmitText
|
||||
</MudButton>
|
||||
}
|
||||
</MudForm>
|
||||
<Issues IssuesData="@(this.inputIssues)"/>
|
||||
<InnerScrolling>
|
||||
<ChildContent>
|
||||
<MudForm @ref="@(this.form)" @bind-IsValid="@(this.inputIsValid)" @bind-Errors="@(this.inputIssues)" FieldChanged="@this.TriggerFormChange" Class="pr-2">
|
||||
<MudText Typo="Typo.body1" Align="Align.Justify" Class="mb-6">
|
||||
@this.Description
|
||||
</MudText>
|
||||
|
||||
@if (this.ShowDedicatedProgress && this.isProcessing)
|
||||
{
|
||||
<MudProgressLinear Color="Color.Primary" Indeterminate="true" Class="mb-6" />
|
||||
}
|
||||
|
||||
<div id="@RESULT_DIV_ID" class="mr-2 mt-3">
|
||||
</div>
|
||||
|
||||
<div id="@BEFORE_RESULT_DIV_ID" class="mt-3">
|
||||
</div>
|
||||
|
||||
@if (this.ShowResult && !this.ShowEntireChatThread && this.resultingContentBlock is not null)
|
||||
{
|
||||
<ContentBlockComponent Role="@(this.resultingContentBlock.Role)" Type="@(this.resultingContentBlock.ContentType)" Time="@(this.resultingContentBlock.Time)" Content="@(this.resultingContentBlock.Content)"/>
|
||||
}
|
||||
|
||||
@if(this.ShowResult && this.ShowEntireChatThread && this.chatThread is not null)
|
||||
{
|
||||
foreach (var block in this.chatThread.Blocks.OrderBy(n => n.Time))
|
||||
{
|
||||
@if (!block.HideFromUser)
|
||||
@if (this.Body is not null)
|
||||
{
|
||||
<ContentBlockComponent Role="@block.Role" Type="@block.ContentType" Time="@block.Time" Content="@block.Content"/>
|
||||
<CascadingValue Value="@this">
|
||||
@this.Body
|
||||
</CascadingValue>
|
||||
|
||||
<MudButton Disabled="@this.SubmitDisabled" Variant="Variant.Filled" Class="mb-3" OnClick="() => this.SubmitAction()" Style="@this.SubmitButtonStyle">
|
||||
@this.SubmitText
|
||||
</MudButton>
|
||||
}
|
||||
</MudForm>
|
||||
<Issues IssuesData="@(this.inputIssues)"/>
|
||||
|
||||
@if (this.ShowDedicatedProgress && this.isProcessing)
|
||||
{
|
||||
<MudProgressLinear Color="Color.Primary" Indeterminate="true" Class="mb-6" />
|
||||
}
|
||||
}
|
||||
|
||||
<div id="@AFTER_RESULT_DIV_ID" class="mt-3">
|
||||
</div>
|
||||
</ChildContent>
|
||||
<FooterContent>
|
||||
<MudStack Row="@true" Wrap="Wrap.Wrap" Class="ma-1">
|
||||
|
||||
@if (!this.FooterButtons.Any(x => x.Type is ButtonTypes.SEND_TO))
|
||||
<div id="@RESULT_DIV_ID" class="mr-2 mt-3">
|
||||
</div>
|
||||
|
||||
<div id="@BEFORE_RESULT_DIV_ID" class="mt-3">
|
||||
</div>
|
||||
|
||||
@if (this.ShowResult && !this.ShowEntireChatThread && this.resultingContentBlock is not null)
|
||||
{
|
||||
@if (this.ShowSendTo)
|
||||
<ContentBlockComponent Role="@(this.resultingContentBlock.Role)" Type="@(this.resultingContentBlock.ContentType)" Time="@(this.resultingContentBlock.Time)" Content="@(this.resultingContentBlock.Content)"/>
|
||||
}
|
||||
|
||||
@if(this.ShowResult && this.ShowEntireChatThread && this.chatThread is not null)
|
||||
{
|
||||
foreach (var block in this.chatThread.Blocks.OrderBy(n => n.Time))
|
||||
{
|
||||
<MudMenu StartIcon="@Icons.Material.Filled.Apps" EndIcon="@Icons.Material.Filled.KeyboardArrowDown" Label="Send to ..." Variant="Variant.Filled" Style="@this.GetSendToColor()" Class="rounded">
|
||||
@foreach (var assistant in Enum.GetValues<Components>().Where(n => n.AllowSendTo()).OrderBy(n => n.Name().Length))
|
||||
{
|
||||
<MudMenuItem OnClick="() => this.SendToAssistant(assistant, new())">
|
||||
@assistant.Name()
|
||||
</MudMenuItem>
|
||||
}
|
||||
</MudMenu>
|
||||
@if (!block.HideFromUser)
|
||||
{
|
||||
<ContentBlockComponent Role="@block.Role" Type="@block.ContentType" Time="@block.Time" Content="@block.Content"/>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@foreach (var button in this.FooterButtons)
|
||||
{
|
||||
switch (button)
|
||||
|
||||
<div id="@AFTER_RESULT_DIV_ID" class="mt-3">
|
||||
</div>
|
||||
</ChildContent>
|
||||
<FooterContent>
|
||||
<MudStack Row="@true" Wrap="Wrap.Wrap" Class="ma-1">
|
||||
|
||||
@if (!this.FooterButtons.Any(x => x.Type is ButtonTypes.SEND_TO))
|
||||
{
|
||||
case ButtonData buttonData when !string.IsNullOrWhiteSpace(buttonData.Tooltip):
|
||||
<MudTooltip Text="@buttonData.Tooltip">
|
||||
<MudButton Variant="Variant.Filled" Color="@buttonData.Color" StartIcon="@GetButtonIcon(buttonData.Icon)" OnClick="async () => await buttonData.AsyncAction()">
|
||||
@buttonData.Text
|
||||
</MudButton>
|
||||
</MudTooltip>
|
||||
break;
|
||||
|
||||
case ButtonData buttonData:
|
||||
<MudButton Variant="Variant.Filled" Color="@buttonData.Color" StartIcon="@GetButtonIcon(buttonData.Icon)" OnClick="async () => await buttonData.AsyncAction()">
|
||||
@buttonData.Text
|
||||
</MudButton>
|
||||
break;
|
||||
|
||||
case SendToButton sendToButton:
|
||||
@if (this.ShowSendTo)
|
||||
{
|
||||
<MudMenu StartIcon="@Icons.Material.Filled.Apps" EndIcon="@Icons.Material.Filled.KeyboardArrowDown" Label="Send to ..." Variant="Variant.Filled" Style="@this.GetSendToColor()" Class="rounded">
|
||||
@foreach (var assistant in Enum.GetValues<Components>().Where(n => n.AllowSendTo()).OrderBy(n => n.Name().Length))
|
||||
{
|
||||
<MudMenuItem OnClick="() => this.SendToAssistant(assistant, sendToButton)">
|
||||
<MudMenuItem OnClick="() => this.SendToAssistant(assistant, new())">
|
||||
@assistant.Name()
|
||||
</MudMenuItem>
|
||||
}
|
||||
</MudMenu>
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@if (this.ShowCopyResult)
|
||||
{
|
||||
<MudButton Variant="Variant.Filled" StartIcon="@Icons.Material.Filled.ContentCopy" OnClick="() => this.CopyToClipboard()">
|
||||
Copy result
|
||||
</MudButton>
|
||||
}
|
||||
|
||||
@if (this.ShowReset)
|
||||
{
|
||||
<MudButton Variant="Variant.Filled" Style="@this.GetResetColor()" StartIcon="@Icons.Material.Filled.Refresh" OnClick="() => this.InnerResetForm()">
|
||||
Reset
|
||||
</MudButton>
|
||||
}
|
||||
@foreach (var button in this.FooterButtons)
|
||||
{
|
||||
switch (button)
|
||||
{
|
||||
case ButtonData buttonData when !string.IsNullOrWhiteSpace(buttonData.Tooltip):
|
||||
<MudTooltip Text="@buttonData.Tooltip">
|
||||
<MudButton Variant="Variant.Filled" Color="@buttonData.Color" StartIcon="@GetButtonIcon(buttonData.Icon)" OnClick="async () => await buttonData.AsyncAction()">
|
||||
@buttonData.Text
|
||||
</MudButton>
|
||||
</MudTooltip>
|
||||
break;
|
||||
|
||||
@if (this.SettingsManager.ConfigurationData.LLMProviders.ShowProviderConfidence)
|
||||
{
|
||||
<ConfidenceInfo Mode="ConfidenceInfoMode.BUTTON" LLMProvider="@this.providerSettings.UsedLLMProvider"/>
|
||||
}
|
||||
case ButtonData buttonData:
|
||||
<MudButton Variant="Variant.Filled" Color="@buttonData.Color" StartIcon="@GetButtonIcon(buttonData.Icon)" OnClick="async () => await buttonData.AsyncAction()">
|
||||
@buttonData.Text
|
||||
</MudButton>
|
||||
break;
|
||||
|
||||
@if (this.AllowProfiles && this.ShowProfileSelection)
|
||||
{
|
||||
<ProfileSelection MarginLeft="" @bind-CurrentProfile="@this.currentProfile"/>
|
||||
}
|
||||
</MudStack>
|
||||
</FooterContent>
|
||||
</InnerScrolling>
|
||||
case SendToButton sendToButton:
|
||||
<MudMenu StartIcon="@Icons.Material.Filled.Apps" EndIcon="@Icons.Material.Filled.KeyboardArrowDown" Label="Send to ..." Variant="Variant.Filled" Style="@this.GetSendToColor()" Class="rounded">
|
||||
@foreach (var assistant in Enum.GetValues<Components>().Where(n => n.AllowSendTo()).OrderBy(n => n.Name().Length))
|
||||
{
|
||||
<MudMenuItem OnClick="() => this.SendToAssistant(assistant, sendToButton)">
|
||||
@assistant.Name()
|
||||
</MudMenuItem>
|
||||
}
|
||||
</MudMenu>
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@if (this.ShowCopyResult)
|
||||
{
|
||||
<MudButton Variant="Variant.Filled" StartIcon="@Icons.Material.Filled.ContentCopy" OnClick="() => this.CopyToClipboard()">
|
||||
Copy result
|
||||
</MudButton>
|
||||
}
|
||||
|
||||
@if (this.ShowReset)
|
||||
{
|
||||
<MudButton Variant="Variant.Filled" Style="@this.GetResetColor()" StartIcon="@Icons.Material.Filled.Refresh" OnClick="() => this.InnerResetForm()">
|
||||
Reset
|
||||
</MudButton>
|
||||
}
|
||||
|
||||
@if (this.SettingsManager.ConfigurationData.LLMProviders.ShowProviderConfidence)
|
||||
{
|
||||
<ConfidenceInfo Mode="PopoverTriggerMode.BUTTON" LLMProvider="@this.providerSettings.UsedLLMProvider"/>
|
||||
}
|
||||
|
||||
@if (this.AllowProfiles && this.ShowProfileSelection)
|
||||
{
|
||||
<ProfileSelection MarginLeft="" @bind-CurrentProfile="@this.currentProfile"/>
|
||||
}
|
||||
</MudStack>
|
||||
</FooterContent>
|
||||
</InnerScrolling>
|
||||
</div>
|
||||
@@ -1,12 +1,12 @@
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
using MudBlazor.Utilities;
|
||||
|
||||
using RustService = AIStudio.Tools.RustService;
|
||||
using Timer = System.Timers.Timer;
|
||||
|
||||
namespace AIStudio.Assistants;
|
||||
@@ -28,6 +28,9 @@ public abstract partial class AssistantBase : ComponentBase, IMessageBusReceiver
|
||||
[Inject]
|
||||
protected RustService RustService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
protected DataSourceService DataSourceService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
protected NavigationManager NavigationManager { get; init; } = null!;
|
||||
|
||||
@@ -97,6 +100,7 @@ public abstract partial class AssistantBase : ComponentBase, IMessageBusReceiver
|
||||
protected bool inputIsValid;
|
||||
protected Profile currentProfile = Profile.NO_PROFILE;
|
||||
protected ChatThread? chatThread;
|
||||
protected IContent? lastUserPrompt;
|
||||
|
||||
private readonly Timer formChangeTimer = new(TimeSpan.FromSeconds(1.6));
|
||||
|
||||
@@ -147,6 +151,8 @@ public abstract partial class AssistantBase : ComponentBase, IMessageBusReceiver
|
||||
|
||||
#region Implementation of IMessageBusReceiver
|
||||
|
||||
public string ComponentName => nameof(AssistantBase);
|
||||
|
||||
public Task ProcessMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data)
|
||||
{
|
||||
switch (triggeredEvent)
|
||||
@@ -240,16 +246,18 @@ public abstract partial class AssistantBase : ComponentBase, IMessageBusReceiver
|
||||
protected DateTimeOffset AddUserRequest(string request, bool hideContentFromUser = false)
|
||||
{
|
||||
var time = DateTimeOffset.Now;
|
||||
this.lastUserPrompt = new ContentText
|
||||
{
|
||||
Text = request,
|
||||
};
|
||||
|
||||
this.chatThread!.Blocks.Add(new ContentBlock
|
||||
{
|
||||
Time = time,
|
||||
ContentType = ContentType.TEXT,
|
||||
HideFromUser = hideContentFromUser,
|
||||
Role = ChatRole.USER,
|
||||
Content = new ContentText
|
||||
{
|
||||
Text = request,
|
||||
},
|
||||
Content = this.lastUserPrompt,
|
||||
});
|
||||
|
||||
return time;
|
||||
@@ -285,7 +293,7 @@ public abstract partial class AssistantBase : ComponentBase, IMessageBusReceiver
|
||||
// Use the selected provider to get the AI response.
|
||||
// By awaiting this line, we wait for the entire
|
||||
// content to be streamed.
|
||||
await aiText.CreateFromProviderAsync(this.providerSettings.CreateProvider(this.Logger), this.SettingsManager, this.providerSettings.Model, this.chatThread);
|
||||
await aiText.CreateFromProviderAsync(this.providerSettings.CreateProvider(this.Logger), this.providerSettings.Model, this.lastUserPrompt, this.chatThread);
|
||||
|
||||
this.isProcessing = false;
|
||||
this.StateHasChanged();
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
@using MudExtensions
|
||||
@inherits AssistantBaseCore
|
||||
|
||||
<MudText Typo="Typo.body1" Class="mb-3">
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
|
||||
You can imagine it like this: Hypothetically, when Wikipedia implemented the ERI, it would vectorize
|
||||
all pages using an embedding method. All of Wikipedia’s data would remain with Wikipedia, including the
|
||||
vector database (decentralized approach). Then, any AI Studio user could add Wikipedia as a data source to
|
||||
significantly reduce the hallucination of the LLM in knowledge questions.
|
||||
</MudText>
|
||||
</MudJustifiedText>
|
||||
|
||||
<MudText Typo="Typo.body1">
|
||||
<b>Related links:</b>
|
||||
@@ -25,10 +25,10 @@
|
||||
ERI server presets
|
||||
</MudText>
|
||||
|
||||
<MudText Typo="Typo.body1" Class="mb-3">
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
|
||||
Here you have the option to save different configurations for various ERI servers and switch between them. This is useful if
|
||||
you are responsible for multiple ERI servers.
|
||||
</MudText>
|
||||
</MudJustifiedText>
|
||||
|
||||
@if(this.SettingsManager.ConfigurationData.ERI.ERIServers.Count is 0)
|
||||
{
|
||||
@@ -52,33 +52,33 @@ else
|
||||
<MudButton Disabled="@this.AreServerPresetsBlocked" OnClick="@this.AddERIServer" Variant="Variant.Filled" Color="Color.Primary">
|
||||
Add ERI server preset
|
||||
</MudButton>
|
||||
<MudButton OnClick="@this.RemoveERIServer" Disabled="@(this.AreServerPresetsBlocked || this.IsNoneERIServerSelected)" Variant="Variant.Filled" Color="Color.Primary">
|
||||
<MudButton OnClick="@this.RemoveERIServer" Disabled="@(this.AreServerPresetsBlocked || this.IsNoneERIServerSelected)" Variant="Variant.Filled" Color="Color.Error">
|
||||
Delete this server preset
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
|
||||
@if(this.AreServerPresetsBlocked)
|
||||
{
|
||||
<MudText Typo="Typo.body1" Class="mb-3 mt-3">
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mb-3 mt-3">
|
||||
Hint: to allow this assistant to manage multiple presets, you must enable the preselection of values in the settings.
|
||||
</MudText>
|
||||
</MudJustifiedText>
|
||||
}
|
||||
|
||||
<MudText Typo="Typo.h4" Class="mb-3 mt-6">
|
||||
Auto save
|
||||
</MudText>
|
||||
|
||||
<MudText Typo="Typo.body1" Class="mb-3">
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
|
||||
The ERI specification will change over time. You probably want to keep your ERI server up to date. This means you might want to
|
||||
regenerate the code for your ERI server. To avoid having to make all inputs each time, all your inputs and decisions can be
|
||||
automatically saved. Would you like this?
|
||||
</MudText>
|
||||
</MudJustifiedText>
|
||||
|
||||
@if(this.AreServerPresetsBlocked)
|
||||
{
|
||||
<MudText Typo="Typo.body1" Class="mb-3">
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
|
||||
Hint: to allow this assistant to automatically save your changes, you must enable the preselection of values in the settings.
|
||||
</MudText>
|
||||
</MudJustifiedText>
|
||||
}
|
||||
|
||||
<MudTextSwitch Label="Should we automatically save any input made?" Disabled="@this.AreServerPresetsBlocked" @bind-Value="@this.autoSave" LabelOn="Yes, please save my inputs" LabelOff="No, I will enter everything again or configure it manually in the settings" />
|
||||
@@ -204,18 +204,18 @@ else
|
||||
Embedding settings
|
||||
</MudText>
|
||||
|
||||
<MudText Typo="Typo.body1" Class="mb-2">
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mb-2">
|
||||
You will likely use one or more embedding methods to encode the meaning of your data into a typically high-dimensional vector
|
||||
space. In this case, you will use a vector database to store and search these vectors (called embeddings). However, you don't
|
||||
have to use embedding methods. When your retrieval method works without any embedding, you can ignore this section. An example: You
|
||||
store files on a file server, and your retrieval method works exclusively with file names in the file system, so you don't
|
||||
need embeddings.
|
||||
</MudText>
|
||||
</MudJustifiedText>
|
||||
|
||||
<MudText Typo="Typo.body1" Class="mb-3">
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
|
||||
You can specify more than one embedding method. This can be useful when you want to use different embeddings for different queries
|
||||
or data types. For example, one embedding for texts, another for images, and a third for videos, etc.
|
||||
</MudText>
|
||||
</MudJustifiedText>
|
||||
|
||||
@if (!this.IsNoneERIServerSelected)
|
||||
{
|
||||
@@ -258,12 +258,12 @@ else
|
||||
Data retrieval settings
|
||||
</MudText>
|
||||
|
||||
<MudText Typo="Typo.body1" Class="mb-2">
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mb-2">
|
||||
For your ERI server, you need to retrieve data that matches a chat or prompt in some way. We call this the retrieval process.
|
||||
You must describe at least one such process. You may offer several retrieval processes from which users can choose. This allows
|
||||
you to test with beta users which process works better. Or you might generally want to give users the choice so they can select
|
||||
the process that best suits their circumstances.
|
||||
</MudText>
|
||||
</MudJustifiedText>
|
||||
|
||||
@if (!this.IsNoneERIServerSelected)
|
||||
{
|
||||
@@ -299,13 +299,13 @@ else
|
||||
Add Retrieval Process
|
||||
</MudButton>
|
||||
|
||||
<MudText Typo="Typo.body1" Class="mb-1">
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mb-1">
|
||||
You can integrate additional libraries. Perhaps you want to evaluate the prompts in advance using a machine learning method or analyze them with a text
|
||||
mining approach? Or maybe you want to preprocess images in the prompts? For such advanced scenarios, you can specify which libraries you want to use here.
|
||||
It's best to describe which library you want to integrate for which purpose. This way, the LLM that writes the ERI server for you can try to use these
|
||||
libraries effectively. This should result in less rework being necessary. If you don't know the necessary libraries, you can instead attempt to describe
|
||||
the intended use. The LLM can then attempt to choose suitable libraries. However, hallucinations can occur, and fictional libraries might be selected.
|
||||
</MudText>
|
||||
</MudJustifiedText>
|
||||
|
||||
<MudTextField Disabled="@this.IsNoneERIServerSelected" T="string" @bind-Text="@this.additionalLibraries" Label="(Optional) Additional libraries" HelperText="Do you want to include additional libraries? Then name them and briefly describe what you want to achieve with them." Variant="Variant.Outlined" Margin="Margin.Normal" Lines="3" AutoGrow="@true" MaxLines="12" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3"/>
|
||||
|
||||
@@ -313,17 +313,17 @@ else
|
||||
Provider selection for generation
|
||||
</MudText>
|
||||
|
||||
<MudText Typo="Typo.body1" Class="mb-2">
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mb-2">
|
||||
The task of writing the ERI server for you is very complex. Therefore, a very powerful LLM is needed to successfully accomplish this task.
|
||||
Small local models will probably not be sufficient. Instead, try using a large cloud-based or a large self-hosted model.
|
||||
</MudText>
|
||||
</MudJustifiedText>
|
||||
|
||||
<MudText Typo="Typo.body1" Class="mb-2">
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mb-2">
|
||||
<b>Important:</b> The LLM may need to generate many files. This reaches the request limit of most providers. Typically, only a certain number
|
||||
of requests can be made per minute, and only a maximum number of tokens can be generated per minute. AI Studio automatically considers this.
|
||||
<b>However, generating all the files takes a certain amount of time.</b> Local or self-hosted models may work without these limitations
|
||||
and can generate responses faster. AI Studio dynamically adapts its behavior and always tries to achieve the fastest possible data processing.
|
||||
</MudText>
|
||||
</MudJustifiedText>
|
||||
|
||||
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
|
||||
|
||||
@@ -331,19 +331,19 @@ else
|
||||
Write code to file system
|
||||
</MudText>
|
||||
|
||||
<MudText Typo="Typo.body1" Class="mb-2">
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mb-2">
|
||||
AI Studio can save the generated code to the file system. You can select a base folder for this. AI Studio ensures that no files are created
|
||||
outside of this base folder. Furthermore, we recommend that you create a Git repository in this folder. This way, you can see what changes the
|
||||
AI has made in which files.
|
||||
</MudText>
|
||||
</MudJustifiedText>
|
||||
|
||||
<MudText Typo="Typo.body1" Class="mb-2">
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mb-2">
|
||||
When you rebuild / re-generate the ERI server code, AI Studio proceeds as follows: All files generated last time will be deleted. All
|
||||
other files you have created remain. Then, the AI generates the new files. <b>But beware:</b> It may happen that the AI generates a
|
||||
file this time that you manually created last time. In this case, your manually created file will then be overwritten. Therefore,
|
||||
you should always create a Git repository and commit or revert all changes before using this assistant. With a diff visualization,
|
||||
you can immediately see where the AI has made changes. It is best to use an IDE suitable for your selected language for this purpose.
|
||||
</MudText>
|
||||
</MudJustifiedText>
|
||||
|
||||
<MudTextSwitch Label="Should we write the generated code to the file system?" Disabled="@this.IsNoneERIServerSelected" @bind-Value="@this.writeToFilesystem" LabelOn="Yes, please write or update all generated code to the file system" LabelOff="No, just show me the code" />
|
||||
<SelectDirectory Label="Base directory where to write the code" @bind-Directory="@this.baseDirectory" Disabled="@(this.IsNoneERIServerSelected || !this.writeToFilesystem)" DirectoryDialogTitle="Select the target directory for the ERI server"/>
|
||||
<SelectDirectory Label="Base directory where to write the code" @bind-Directory="@this.baseDirectory" Disabled="@(this.IsNoneERIServerSelected || !this.writeToFilesystem)" DirectoryDialogTitle="Select the target directory for the ERI server" Validation="@this.ValidateDirectory" />
|
||||
@@ -299,6 +299,8 @@ public partial class AssistantERI : AssistantBaseCore
|
||||
protected override IReadOnlyList<IButtonData> FooterButtons => [];
|
||||
|
||||
protected override bool ShowEntireChatThread => true;
|
||||
|
||||
protected override bool ShowSendTo => false;
|
||||
|
||||
protected override string SubmitText => "Create the ERI server";
|
||||
|
||||
@@ -476,6 +478,16 @@ public partial class AssistantERI : AssistantBaseCore
|
||||
if(this.selectedERIServer is null)
|
||||
return;
|
||||
|
||||
var dialogParameters = new DialogParameters
|
||||
{
|
||||
{ "Message", $"Are you sure you want to delete the ERI server preset '{this.selectedERIServer.ServerName}'?" },
|
||||
};
|
||||
|
||||
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Delete ERI server preset", dialogParameters, DialogOptions.FULLSCREEN);
|
||||
var dialogResult = await dialogReference.Result;
|
||||
if (dialogResult is null || dialogResult.Canceled)
|
||||
return;
|
||||
|
||||
this.SettingsManager.ConfigurationData.ERI.ERIServers.Remove(this.selectedERIServer);
|
||||
this.selectedERIServer = null;
|
||||
this.ResetForm();
|
||||
@@ -739,6 +751,17 @@ public partial class AssistantERI : AssistantBaseCore
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? ValidateDirectory(string path)
|
||||
{
|
||||
if(!this.writeToFilesystem)
|
||||
return null;
|
||||
|
||||
if(string.IsNullOrWhiteSpace(path))
|
||||
return "Please provide a base directory for the ERI server to write files to.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string GetMultiSelectionAuthText(List<Auth> selectedValues)
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@ public static class ERIVersionExtensions
|
||||
try
|
||||
{
|
||||
var url = version.SpecificationURL();
|
||||
var response = await httpClient.GetAsync(url);
|
||||
using var response = await httpClient.GetAsync(url);
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
catch
|
||||
|
||||
@@ -12,6 +12,7 @@ public enum ChatRole
|
||||
USER,
|
||||
AI,
|
||||
AGENT,
|
||||
RAG,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using AIStudio.Components;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Settings.DataModel;
|
||||
using AIStudio.Tools.ERIClient.DataModel;
|
||||
|
||||
namespace AIStudio.Chat;
|
||||
|
||||
@@ -27,6 +30,16 @@ public sealed record ChatThread
|
||||
/// </summary>
|
||||
public string SelectedProfile { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The data source options for this chat thread.
|
||||
/// </summary>
|
||||
public DataSourceOptions DataSourceOptions { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The AI-selected data sources for this chat thread.
|
||||
/// </summary>
|
||||
public IReadOnlyList<DataSourceAgentSelected> AISelectedDataSources { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// The name of the chat thread. Usually generated by an AI model or manually edited by the user.
|
||||
/// </summary>
|
||||
@@ -138,4 +151,46 @@ public sealed record ChatThread
|
||||
// Remove the block from the chat thread:
|
||||
this.Blocks.Remove(block);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transforms this chat thread to an ERI chat thread.
|
||||
/// </summary>
|
||||
/// <param name="token">The cancellation token.</param>
|
||||
/// <returns>The ERI chat thread.</returns>
|
||||
public async Task<Tools.ERIClient.DataModel.ChatThread> ToERIChatThread(CancellationToken token = default)
|
||||
{
|
||||
//
|
||||
// Transform the content blocks:
|
||||
//
|
||||
var contentBlocks = new List<Tools.ERIClient.DataModel.ContentBlock>(this.Blocks.Count);
|
||||
foreach (var block in this.Blocks)
|
||||
{
|
||||
var (contentData, contentType) = block.Content switch
|
||||
{
|
||||
ContentImage image => (await image.AsBase64(token), Tools.ERIClient.DataModel.ContentType.IMAGE),
|
||||
ContentText text => (text.Text, Tools.ERIClient.DataModel.ContentType.TEXT),
|
||||
|
||||
_ => (string.Empty, Tools.ERIClient.DataModel.ContentType.UNKNOWN),
|
||||
};
|
||||
|
||||
contentBlocks.Add(new Tools.ERIClient.DataModel.ContentBlock
|
||||
{
|
||||
Role = block.Role switch
|
||||
{
|
||||
ChatRole.AI => Role.AI,
|
||||
ChatRole.USER => Role.USER,
|
||||
ChatRole.AGENT => Role.AGENT,
|
||||
ChatRole.SYSTEM => Role.SYSTEM,
|
||||
ChatRole.NONE => Role.NONE,
|
||||
|
||||
_ => Role.UNKNOW,
|
||||
},
|
||||
|
||||
Content = contentData,
|
||||
Type = contentType,
|
||||
});
|
||||
}
|
||||
|
||||
return new Tools.ERIClient.DataModel.ChatThread { ContentBlocks = contentBlocks };
|
||||
}
|
||||
}
|
||||
@@ -78,9 +78,9 @@
|
||||
break;
|
||||
|
||||
case ContentType.IMAGE:
|
||||
if (this.Content is ContentImage imageContent)
|
||||
if (this.Content is ContentImage { SourceType: ContentImageSource.URL or ContentImageSource.LOCAL_PATH } imageContent)
|
||||
{
|
||||
<MudImage Src="@imageContent.URL"/>
|
||||
<MudImage Src="@imageContent.Source"/>
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
using RustService = AIStudio.Tools.RustService;
|
||||
|
||||
namespace AIStudio.Chat;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Settings;
|
||||
|
||||
namespace AIStudio.Chat;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an image inside the chat.
|
||||
/// </summary>
|
||||
public sealed class ContentImage : IContent
|
||||
public sealed class ContentImage : IContent, IImageSource
|
||||
{
|
||||
#region Implementation of IContent
|
||||
|
||||
@@ -29,7 +28,7 @@ public sealed class ContentImage : IContent
|
||||
public Func<Task> StreamingEvent { get; set; } = () => Task.CompletedTask;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task CreateFromProviderAsync(IProvider provider, SettingsManager settings, Model chatModel, ChatThread chatChatThread, CancellationToken token = default)
|
||||
public Task CreateFromProviderAsync(IProvider provider, Model chatModel, IContent? lastPrompt, ChatThread? chatChatThread, CancellationToken token = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@@ -37,12 +36,15 @@ public sealed class ContentImage : IContent
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// The URL of the image.
|
||||
/// The type of the image source.
|
||||
/// </summary>
|
||||
public string URL { get; set; } = string.Empty;
|
||||
/// <remarks>
|
||||
/// Is the image source a URL, a local file path, a base64 string, etc.?
|
||||
/// </remarks>
|
||||
public required ContentImageSource SourceType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The local path of the image.
|
||||
/// The image source.
|
||||
/// </summary>
|
||||
public string LocalPath { get; set; } = string.Empty;
|
||||
public required string Source { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace AIStudio.Chat;
|
||||
|
||||
public enum ContentImageSource
|
||||
{
|
||||
URL,
|
||||
LOCAL_PATH,
|
||||
BASE64,
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using System.Text.Json.Serialization;
|
||||
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools.RAG.RAGProcesses;
|
||||
|
||||
namespace AIStudio.Chat;
|
||||
|
||||
@@ -35,10 +36,17 @@ public sealed class ContentText : IContent
|
||||
public Func<Task> StreamingEvent { get; set; } = () => Task.CompletedTask;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task CreateFromProviderAsync(IProvider provider, SettingsManager settings, Model chatModel, ChatThread? chatThread, CancellationToken token = default)
|
||||
public async Task CreateFromProviderAsync(IProvider provider, Model chatModel, IContent? lastPrompt, ChatThread? chatThread, CancellationToken token = default)
|
||||
{
|
||||
if(chatThread is null)
|
||||
return;
|
||||
|
||||
// Call the RAG process. Right now, we only have one RAG process:
|
||||
if (lastPrompt is not null)
|
||||
{
|
||||
var rag = new AISrcSelWithRetCtxVal();
|
||||
chatThread = await rag.ProcessAsync(provider, lastPrompt, chatThread, token);
|
||||
}
|
||||
|
||||
// Store the last time we got a response. We use this later
|
||||
// to determine whether we should notify the UI about the
|
||||
@@ -46,6 +54,9 @@ public sealed class ContentText : IContent
|
||||
// the user chose.
|
||||
var last = DateTimeOffset.Now;
|
||||
|
||||
// Get the settings manager:
|
||||
var settings = Program.SERVICE_PROVIDER.GetService<SettingsManager>()!;
|
||||
|
||||
// Start another thread by using a task to uncouple
|
||||
// the UI thread from the AI processing:
|
||||
await Task.Run(async () =>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Settings;
|
||||
|
||||
namespace AIStudio.Chat;
|
||||
|
||||
@@ -42,5 +41,16 @@ public interface IContent
|
||||
/// <summary>
|
||||
/// Uses the provider to create the content.
|
||||
/// </summary>
|
||||
public Task CreateFromProviderAsync(IProvider provider, SettingsManager settings, Model chatModel, ChatThread chatChatThread, CancellationToken token = default);
|
||||
public Task CreateFromProviderAsync(IProvider provider, Model chatModel, IContent? lastPrompt, ChatThread? chatChatThread, CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the corresponding ERI content type.
|
||||
/// </summary>
|
||||
public Tools.ERIClient.DataModel.ContentType ToERIContentType => this switch
|
||||
{
|
||||
ContentText => Tools.ERIClient.DataModel.ContentType.TEXT,
|
||||
ContentImage => Tools.ERIClient.DataModel.ContentType.IMAGE,
|
||||
|
||||
_ => Tools.ERIClient.DataModel.ContentType.UNKNOWN,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace AIStudio.Chat;
|
||||
|
||||
public interface IImageSource
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of the image source.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Is the image source a URL, a local file path, a base64 string, etc.?
|
||||
/// </remarks>
|
||||
public ContentImageSource SourceType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The image source.
|
||||
/// </summary>
|
||||
public string Source { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
namespace AIStudio.Chat;
|
||||
|
||||
public static class IImageSourceExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Read the image content as a base64 string.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The images are directly converted to base64 strings. The maximum
|
||||
/// size of the image is around 10 MB. If the image is larger, the method
|
||||
/// returns an empty string.
|
||||
///
|
||||
/// As of now, this method does no sort of image processing. LLMs usually
|
||||
/// do not work with arbitrary image sizes. In the future, we might have
|
||||
/// to resize the images before sending them to the model.
|
||||
/// </remarks>
|
||||
/// <param name="image">The image source.</param>
|
||||
/// <param name="token">The cancellation token.</param>
|
||||
/// <returns>The image content as a base64 string; might be empty.</returns>
|
||||
public static async Task<string> AsBase64(this IImageSource image, CancellationToken token = default)
|
||||
{
|
||||
switch (image.SourceType)
|
||||
{
|
||||
case ContentImageSource.BASE64:
|
||||
return image.Source;
|
||||
|
||||
case ContentImageSource.URL:
|
||||
{
|
||||
using var httpClient = new HttpClient();
|
||||
using var response = await httpClient.GetAsync(image.Source, HttpCompletionOption.ResponseHeadersRead, token);
|
||||
if(response.IsSuccessStatusCode)
|
||||
{
|
||||
// Read the length of the content:
|
||||
var lengthBytes = response.Content.Headers.ContentLength;
|
||||
if(lengthBytes > 10_000_000)
|
||||
return string.Empty;
|
||||
|
||||
var bytes = await response.Content.ReadAsByteArrayAsync(token);
|
||||
return Convert.ToBase64String(bytes);
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
case ContentImageSource.LOCAL_PATH:
|
||||
if(File.Exists(image.Source))
|
||||
{
|
||||
// Read the content length:
|
||||
var length = new FileInfo(image.Source).Length;
|
||||
if(length > 10_000_000)
|
||||
return string.Empty;
|
||||
|
||||
var bytes = await File.ReadAllBytesAsync(image.Source, token);
|
||||
return Convert.ToBase64String(bytes);
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,8 @@ public partial class AssistantBlock : ComponentBase, IMessageBusReceiver, IDispo
|
||||
|
||||
#region Implementation of IMessageBusReceiver
|
||||
|
||||
public string ComponentName => nameof(AssistantBlock);
|
||||
|
||||
public Task ProcessMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data)
|
||||
{
|
||||
switch (triggeredEvent)
|
||||
|
||||
@@ -13,6 +13,12 @@ public partial class Changelog
|
||||
|
||||
public static readonly Log[] LOGS =
|
||||
[
|
||||
new (205, "v0.9.30, build 205 (2025-02-24 19:55 UTC)", "v0.9.30.md"),
|
||||
new (204, "v0.9.29, build 204 (2025-02-24 13:48 UTC)", "v0.9.29.md"),
|
||||
new (203, "v0.9.28, build 203 (2025-02-09 16:33 UTC)", "v0.9.28.md"),
|
||||
new (202, "v0.9.27, build 202 (2025-01-21 18:24 UTC)", "v0.9.27.md"),
|
||||
new (201, "v0.9.26, build 201 (2025-01-13 19:11 UTC)", "v0.9.26.md"),
|
||||
new (200, "v0.9.25, build 200 (2025-01-04 18:33 UTC)", "v0.9.25.md"),
|
||||
new (199, "v0.9.24, build 199 (2025-01-04 11:40 UTC)", "v0.9.24.md"),
|
||||
new (198, "v0.9.23, build 198 (2025-01-02 19:39 UTC)", "v0.9.23.md"),
|
||||
new (197, "v0.9.22, build 197 (2024-12-04 10:58 UTC)", "v0.9.22.md"),
|
||||
|
||||
@@ -23,7 +23,7 @@ public partial class Changelog : ComponentBase
|
||||
|
||||
private async Task ReadLogAsync()
|
||||
{
|
||||
var response = await this.HttpClient.GetAsync($"changelog/{this.SelectedLog.Filename}");
|
||||
using var response = await this.HttpClient.GetAsync($"changelog/{this.SelectedLog.Filename}");
|
||||
this.LogContent = await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
@inherits MSGComponentBase
|
||||
|
||||
<InnerScrolling FillEntireHorizontalSpace="@true" @ref="@this.scrollingArea" HeaderHeight="12.3em" MinWidth="36em">
|
||||
<InnerScrolling FillEntireHorizontalSpace="@true" @ref="@this.scrollingArea" MinWidth="36em" Style="height: 100%">
|
||||
<ChildContent>
|
||||
@if (this.ChatThread is not null)
|
||||
{
|
||||
@@ -97,7 +97,7 @@
|
||||
|
||||
@if (this.SettingsManager.ConfigurationData.LLMProviders.ShowProviderConfidence)
|
||||
{
|
||||
<ConfidenceInfo Mode="ConfidenceInfoMode.ICON" LLMProvider="@this.Provider.UsedLLMProvider"/>
|
||||
<ConfidenceInfo Mode="PopoverTriggerMode.ICON" LLMProvider="@this.Provider.UsedLLMProvider"/>
|
||||
}
|
||||
|
||||
@if (this.isStreaming && this.cancellationTokenSource is not null)
|
||||
@@ -108,6 +108,11 @@
|
||||
}
|
||||
|
||||
<ProfileSelection CurrentProfile="@this.currentProfile" CurrentProfileChanged="@this.ProfileWasChanged"/>
|
||||
|
||||
@if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager))
|
||||
{
|
||||
<DataSourceSelection @ref="@this.dataSourceSelectionComponent" PopoverTriggerMode="PopoverTriggerMode.BUTTON" PopoverButtonClasses="ma-3" LLMProvider="@this.Provider" DataSourceOptions="@this.GetCurrentDataSourceOptions()" DataSourceOptionsChanged="@(async options => await this.SetCurrentDataSourceOptions(options))" DataSourcesAISelected="@this.GetAgentSelectedDataSources()"/>
|
||||
}
|
||||
</MudToolBar>
|
||||
</FooterContent>
|
||||
</InnerScrolling>
|
||||
@@ -3,6 +3,7 @@ using AIStudio.Dialogs;
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Settings.DataModel;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
@@ -20,10 +21,10 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
public EventCallback<ChatThread?> ChatThreadChanged { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Settings.Provider Provider { get; set; }
|
||||
public AIStudio.Settings.Provider Provider { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<Settings.Provider> ProviderChanged { get; set; }
|
||||
public EventCallback<AIStudio.Settings.Provider> ProviderChanged { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Action<string> WorkspaceName { get; set; } = _ => { };
|
||||
@@ -40,9 +41,14 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
[Inject]
|
||||
private IDialogService DialogService { get; init; } = null!;
|
||||
|
||||
private const Placement TOOLBAR_TOOLTIP_PLACEMENT = Placement.Bottom;
|
||||
[Inject]
|
||||
private DataSourceService DataSourceService { get; init; } = null!;
|
||||
|
||||
private const Placement TOOLBAR_TOOLTIP_PLACEMENT = Placement.Top;
|
||||
private static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new();
|
||||
|
||||
private DataSourceSelection? dataSourceSelectionComponent;
|
||||
private DataSourceOptions earlyDataSourceOptions = new();
|
||||
private Profile currentProfile = Profile.NO_PROFILE;
|
||||
private bool hasUnsavedChanges;
|
||||
private bool mustScrollToBottomAfterRender;
|
||||
@@ -66,20 +72,40 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// Apply the filters for the message bus:
|
||||
this.ApplyFilters([], [ Event.HAS_CHAT_UNSAVED_CHANGES, Event.RESET_CHAT_STATE, Event.CHAT_STREAMING_DONE, Event.WORKSPACE_LOADED_CHAT_CHANGED ]);
|
||||
|
||||
// Configure the spellchecking for the user input:
|
||||
this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES);
|
||||
|
||||
|
||||
// Get the preselected profile:
|
||||
this.currentProfile = this.SettingsManager.GetPreselectedProfile(Tools.Components.CHAT);
|
||||
|
||||
//
|
||||
// Check for deferred messages of the kind 'SEND_TO_CHAT',
|
||||
// aka the user sends an assistant result to the chat:
|
||||
//
|
||||
var deferredContent = MessageBus.INSTANCE.CheckDeferredMessages<ChatThread>(Event.SEND_TO_CHAT).FirstOrDefault();
|
||||
if (deferredContent is not null)
|
||||
{
|
||||
//
|
||||
// Yes, the user sent an assistant result to the chat.
|
||||
//
|
||||
|
||||
// Use chat thread sent by the user:
|
||||
this.ChatThread = deferredContent;
|
||||
this.Logger.LogInformation($"The chat '{this.ChatThread.Name}' with {this.ChatThread.Blocks.Count} messages was deferred and will be rendered now.");
|
||||
await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
|
||||
|
||||
// We know already that the chat thread is not null,
|
||||
// but we have to check it again for the nullability
|
||||
// for the compiler:
|
||||
if (this.ChatThread is not null)
|
||||
{
|
||||
//
|
||||
// Check if the chat thread has a name. If not, we
|
||||
// generate the name now:
|
||||
//
|
||||
if (string.IsNullOrWhiteSpace(this.ChatThread.Name))
|
||||
{
|
||||
var firstUserBlock = this.ChatThread.Blocks.FirstOrDefault(x => x.Role == ChatRole.USER);
|
||||
@@ -93,12 +119,24 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Check if the user wants to apply the standard chat data source options:
|
||||
//
|
||||
if (this.SettingsManager.ConfigurationData.Chat.SendToChatDataSourceBehavior is SendToChatDataSourceBehavior.APPLY_STANDARD_CHAT_DATA_SOURCE_OPTIONS)
|
||||
this.ChatThread.DataSourceOptions = this.SettingsManager.ConfigurationData.Chat.PreselectedDataSourceOptions.CreateCopy();
|
||||
|
||||
//
|
||||
// Check if the user wants to store the chat automatically:
|
||||
//
|
||||
if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY)
|
||||
{
|
||||
this.autoSaveEnabled = true;
|
||||
this.mustStoreChat = true;
|
||||
|
||||
// Ensure the workspace exists:
|
||||
//
|
||||
// When a standard workspace is used, we have to ensure
|
||||
// that the workspace is available:
|
||||
//
|
||||
if(this.ChatThread.WorkspaceId == KnownWorkspaces.ERI_SERVER_WORKSPACE_ID)
|
||||
await WorkspaceBehaviour.EnsureERIServerWorkspace();
|
||||
|
||||
@@ -106,22 +144,60 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
await WorkspaceBehaviour.EnsureBiasWorkspace();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.SettingsManager.ConfigurationData.Chat.ShowLatestMessageAfterLoading)
|
||||
{
|
||||
this.mustScrollToBottomAfterRender = true;
|
||||
this.scrollRenderCountdown = 2;
|
||||
this.StateHasChanged();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//
|
||||
// No, the user did not send an assistant result to the chat.
|
||||
//
|
||||
this.ApplyStandardDataSourceOptions();
|
||||
}
|
||||
|
||||
//
|
||||
// Check if the user wants to show the latest message after loading:
|
||||
//
|
||||
if (this.SettingsManager.ConfigurationData.Chat.ShowLatestMessageAfterLoading)
|
||||
{
|
||||
//
|
||||
// We cannot scroll to the bottom right now because the
|
||||
// chat component is not rendered yet. We have to wait for
|
||||
// the rendering process to finish. Thus, we set a flag
|
||||
// to scroll to the bottom after the rendering process.:
|
||||
//
|
||||
this.mustScrollToBottomAfterRender = true;
|
||||
this.scrollRenderCountdown = 4;
|
||||
this.StateHasChanged();
|
||||
}
|
||||
|
||||
//
|
||||
// Check if another component deferred the loading of a chat.
|
||||
//
|
||||
// This is used, e.g., for the bias-of-the-day component:
|
||||
// when the bias for this day was already produced, the bias
|
||||
// component sends a message to the chat component to load
|
||||
// the chat with the bias:
|
||||
//
|
||||
var deferredLoading = MessageBus.INSTANCE.CheckDeferredMessages<LoadChat>(Event.LOAD_CHAT).FirstOrDefault();
|
||||
if (deferredLoading != default)
|
||||
{
|
||||
this.loadChat = deferredLoading;
|
||||
this.mustLoadChat = true;
|
||||
this.Logger.LogInformation($"The loading of the chat '{this.loadChat.ChatId}' was deferred and will be loaded now.");
|
||||
}
|
||||
|
||||
//
|
||||
// When for whatever reason we have a chat thread, we have to
|
||||
// ensure that the corresponding workspace id is set and the
|
||||
// workspace name is loaded:
|
||||
//
|
||||
if (this.ChatThread is not null)
|
||||
{
|
||||
this.currentWorkspaceId = this.ChatThread.WorkspaceId;
|
||||
this.currentWorkspaceName = await WorkspaceBehaviour.LoadWorkspaceName(this.ChatThread.WorkspaceId);
|
||||
this.WorkspaceName(this.currentWorkspaceName);
|
||||
}
|
||||
|
||||
// Select the correct provider:
|
||||
await this.SelectProviderWhenLoadingChat();
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
@@ -144,18 +220,23 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
|
||||
if (firstRender && this.mustLoadChat)
|
||||
{
|
||||
this.Logger.LogInformation($"Try to load the chat '{this.loadChat.ChatId}' now.");
|
||||
this.mustLoadChat = false;
|
||||
this.ChatThread = await WorkspaceBehaviour.LoadChat(this.loadChat);
|
||||
|
||||
if(this.ChatThread is not null)
|
||||
{
|
||||
await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
|
||||
this.Logger.LogInformation($"The chat '{this.ChatThread!.ChatId}' with title '{this.ChatThread.Name}' ({this.ChatThread.Blocks.Count} messages) was loaded successfully.");
|
||||
|
||||
this.currentWorkspaceName = await WorkspaceBehaviour.LoadWorkspaceName(this.ChatThread.WorkspaceId);
|
||||
this.WorkspaceName(this.currentWorkspaceName);
|
||||
await this.SelectProviderWhenLoadingChat();
|
||||
}
|
||||
else
|
||||
this.Logger.LogWarning($"The chat '{this.loadChat.ChatId}' could not be loaded.");
|
||||
|
||||
this.StateHasChanged();
|
||||
await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
|
||||
}
|
||||
|
||||
if(this.mustScrollToBottomAfterRender)
|
||||
@@ -190,6 +271,13 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
|
||||
private string UserInputClass => this.SettingsManager.ConfigurationData.LLMProviders.ShowProviderConfidence ? "confidence-border" : string.Empty;
|
||||
|
||||
private void ApplyStandardDataSourceOptions()
|
||||
{
|
||||
var chatDefaultOptions = this.SettingsManager.ConfigurationData.Chat.PreselectedDataSourceOptions.CreateCopy();
|
||||
this.earlyDataSourceOptions = chatDefaultOptions;
|
||||
this.dataSourceSelectionComponent?.ChangeOptionWithoutSaving(chatDefaultOptions);
|
||||
}
|
||||
|
||||
private string ExtractThreadName(string firstUserInput)
|
||||
{
|
||||
// We select the first 10 words of the user input:
|
||||
@@ -217,8 +305,43 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
|
||||
}
|
||||
|
||||
private IReadOnlyList<DataSourceAgentSelected> GetAgentSelectedDataSources()
|
||||
{
|
||||
if (this.ChatThread is null)
|
||||
return [];
|
||||
|
||||
return this.ChatThread.AISelectedDataSources;
|
||||
}
|
||||
|
||||
private DataSourceOptions GetCurrentDataSourceOptions()
|
||||
{
|
||||
if (this.ChatThread is not null)
|
||||
return this.ChatThread.DataSourceOptions;
|
||||
|
||||
return this.earlyDataSourceOptions;
|
||||
}
|
||||
|
||||
private async Task SetCurrentDataSourceOptions(DataSourceOptions updatedOptions)
|
||||
{
|
||||
if (this.ChatThread is not null)
|
||||
{
|
||||
this.hasUnsavedChanges = true;
|
||||
this.ChatThread.DataSourceOptions = updatedOptions;
|
||||
if(this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY)
|
||||
{
|
||||
await this.SaveThread();
|
||||
this.hasUnsavedChanges = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
this.earlyDataSourceOptions = updatedOptions;
|
||||
}
|
||||
|
||||
private async Task InputKeyEvent(KeyboardEventArgs keyEvent)
|
||||
{
|
||||
if(this.dataSourceSelectionComponent?.IsVisible ?? false)
|
||||
this.dataSourceSelectionComponent.Hide();
|
||||
|
||||
this.hasUnsavedChanges = true;
|
||||
var key = keyEvent.Code.ToLowerInvariant();
|
||||
|
||||
@@ -262,6 +385,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
SystemPrompt = SystemPrompts.DEFAULT,
|
||||
WorkspaceId = this.currentWorkspaceId,
|
||||
ChatId = Guid.NewGuid(),
|
||||
DataSourceOptions = this.earlyDataSourceOptions,
|
||||
Name = this.ExtractThreadName(this.userInput),
|
||||
Seed = this.RNG.Next(),
|
||||
Blocks = [],
|
||||
@@ -281,8 +405,14 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
}
|
||||
|
||||
var time = DateTimeOffset.Now;
|
||||
IContent? lastUserPrompt;
|
||||
if (!reuseLastUserPrompt)
|
||||
{
|
||||
lastUserPrompt = new ContentText
|
||||
{
|
||||
Text = this.userInput,
|
||||
};
|
||||
|
||||
//
|
||||
// Add the user message to the thread:
|
||||
//
|
||||
@@ -291,10 +421,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
Time = time,
|
||||
ContentType = ContentType.TEXT,
|
||||
Role = ChatRole.USER,
|
||||
Content = new ContentText
|
||||
{
|
||||
Text = this.userInput,
|
||||
},
|
||||
Content = lastUserPrompt,
|
||||
});
|
||||
|
||||
// Save the chat:
|
||||
@@ -305,6 +432,8 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
this.StateHasChanged();
|
||||
}
|
||||
}
|
||||
else
|
||||
lastUserPrompt = this.ChatThread.Blocks.Last(x => x.Role is ChatRole.USER).Content;
|
||||
|
||||
//
|
||||
// Add the AI response to the thread:
|
||||
@@ -346,7 +475,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
// Use the selected provider to get the AI response.
|
||||
// By awaiting this line, we wait for the entire
|
||||
// content to be streamed.
|
||||
await aiText.CreateFromProviderAsync(this.Provider.CreateProvider(this.Logger), this.SettingsManager, this.Provider.Model, this.ChatThread, this.cancellationTokenSource.Token);
|
||||
await aiText.CreateFromProviderAsync(this.Provider.CreateProvider(this.Logger), this.Provider.Model, lastUserPrompt, this.ChatThread, this.cancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
this.cancellationTokenSource = null;
|
||||
@@ -360,6 +489,8 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
|
||||
// Disable the stream state:
|
||||
this.isStreaming = false;
|
||||
|
||||
// Update the UI:
|
||||
this.StateHasChanged();
|
||||
}
|
||||
|
||||
@@ -393,6 +524,10 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
|
||||
private async Task StartNewChat(bool useSameWorkspace = false, bool deletePreviousChat = false)
|
||||
{
|
||||
//
|
||||
// Want the user to manage the chat storage manually? In that case, we have to ask the user
|
||||
// about possible data loss:
|
||||
//
|
||||
if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_MANUALLY && this.hasUnsavedChanges)
|
||||
{
|
||||
var dialogParameters = new DialogParameters
|
||||
@@ -406,6 +541,9 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Delete the previous chat when desired and necessary:
|
||||
//
|
||||
if (this.ChatThread is not null && deletePreviousChat)
|
||||
{
|
||||
string chatPath;
|
||||
@@ -420,10 +558,16 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
await this.Workspaces.DeleteChat(chatPath, askForConfirmation: false, unloadChat: true);
|
||||
}
|
||||
|
||||
//
|
||||
// Reset our state:
|
||||
//
|
||||
this.isStreaming = false;
|
||||
this.hasUnsavedChanges = false;
|
||||
this.userInput = string.Empty;
|
||||
|
||||
//
|
||||
// Reset the LLM provider considering the user's settings:
|
||||
//
|
||||
switch (this.SettingsManager.ConfigurationData.Chat.AddChatProviderBehavior)
|
||||
{
|
||||
case AddChatProviderBehavior.ADDED_CHATS_USE_DEFAULT_PROVIDER:
|
||||
@@ -442,8 +586,16 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
break;
|
||||
}
|
||||
|
||||
//
|
||||
// Reset the chat thread or create a new one:
|
||||
//
|
||||
if (!useSameWorkspace)
|
||||
{
|
||||
//
|
||||
// When the user wants to start a new chat outside the current workspace,
|
||||
// we have to reset the workspace id and the workspace name. Also, we have
|
||||
// to reset the chat thread:
|
||||
//
|
||||
this.ChatThread = null;
|
||||
this.currentWorkspaceId = Guid.Empty;
|
||||
this.currentWorkspaceName = string.Empty;
|
||||
@@ -451,6 +603,11 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
}
|
||||
else
|
||||
{
|
||||
//
|
||||
// When the user wants to start a new chat in the same workspace, we have to
|
||||
// reset the chat thread only. The workspace id and the workspace name remain
|
||||
// the same:
|
||||
//
|
||||
this.ChatThread = new()
|
||||
{
|
||||
SelectedProvider = this.Provider.Id,
|
||||
@@ -463,8 +620,11 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
Blocks = [],
|
||||
};
|
||||
}
|
||||
|
||||
this.userInput = string.Empty;
|
||||
|
||||
// Now, we have to reset the data source options as well:
|
||||
this.ApplyStandardDataSourceOptions();
|
||||
|
||||
// Notify the parent component about the change:
|
||||
await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
|
||||
}
|
||||
|
||||
@@ -518,19 +678,30 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
this.isStreaming = false;
|
||||
this.hasUnsavedChanges = false;
|
||||
this.userInput = string.Empty;
|
||||
this.currentWorkspaceId = this.ChatThread?.WorkspaceId ?? Guid.Empty;
|
||||
this.currentWorkspaceName = this.ChatThread is null ? string.Empty : await WorkspaceBehaviour.LoadWorkspaceName(this.ChatThread.WorkspaceId);
|
||||
this.WorkspaceName(this.currentWorkspaceName);
|
||||
|
||||
if (this.ChatThread is not null)
|
||||
{
|
||||
this.currentWorkspaceId = this.ChatThread.WorkspaceId;
|
||||
this.currentWorkspaceName = await WorkspaceBehaviour.LoadWorkspaceName(this.ChatThread.WorkspaceId);
|
||||
this.WorkspaceName(this.currentWorkspaceName);
|
||||
this.dataSourceSelectionComponent?.ChangeOptionWithoutSaving(this.ChatThread.DataSourceOptions, this.ChatThread.AISelectedDataSources);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.currentWorkspaceId = Guid.Empty;
|
||||
this.currentWorkspaceName = string.Empty;
|
||||
this.WorkspaceName(this.currentWorkspaceName);
|
||||
this.ApplyStandardDataSourceOptions();
|
||||
}
|
||||
|
||||
await this.SelectProviderWhenLoadingChat();
|
||||
|
||||
this.userInput = string.Empty;
|
||||
if (this.SettingsManager.ConfigurationData.Chat.ShowLatestMessageAfterLoading)
|
||||
{
|
||||
this.mustScrollToBottomAfterRender = true;
|
||||
this.scrollRenderCountdown = 2;
|
||||
this.StateHasChanged();
|
||||
}
|
||||
|
||||
this.StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task ResetState()
|
||||
@@ -544,6 +715,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
this.WorkspaceName(this.currentWorkspaceName);
|
||||
|
||||
this.ChatThread = null;
|
||||
this.ApplyStandardDataSourceOptions();
|
||||
await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
|
||||
}
|
||||
|
||||
@@ -551,7 +723,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
{
|
||||
var chatProvider = this.ChatThread?.SelectedProvider;
|
||||
var chatProfile = this.ChatThread?.SelectedProfile;
|
||||
|
||||
|
||||
switch (this.SettingsManager.ConfigurationData.Chat.LoadingProviderBehavior)
|
||||
{
|
||||
default:
|
||||
@@ -648,6 +820,8 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
|
||||
#region Overrides of MSGComponentBase
|
||||
|
||||
public override string ComponentName => nameof(ChatComponent);
|
||||
|
||||
public override async Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
|
||||
{
|
||||
switch (triggeredEvent)
|
||||
@@ -687,6 +861,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
this.MessageBus.Unregister(this);
|
||||
if(this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY)
|
||||
{
|
||||
await this.SaveThread();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@using AIStudio.Provider
|
||||
<div class="d-flex">
|
||||
<MudTooltip Text="Shows and hides the confidence card with information about the selected LLM provider.">
|
||||
@if (this.Mode is ConfidenceInfoMode.ICON)
|
||||
<MudTooltip Text="Shows and hides the confidence card with information about the selected LLM provider." Placement="Placement.Top">
|
||||
@if (this.Mode is PopoverTriggerMode.ICON)
|
||||
{
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Security" Class="confidence-icon" Style="@this.LLMProvider.GetConfidence(this.SettingsManager).SetColorStyle(this.SettingsManager)" OnClick="@(() => this.ToggleConfidence())"/>
|
||||
}
|
||||
@@ -20,7 +20,7 @@
|
||||
<MudText Typo="Typo.h5">Confidence Card</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudCardContent Style="max-height: 50vh; overflow: auto;">
|
||||
<MudText Typo="Typo.h6">Description</MudText>
|
||||
<MudMarkdown Value="@this.currentConfidence.Description"/>
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace AIStudio.Components;
|
||||
public partial class ConfidenceInfo : ComponentBase, IMessageBusReceiver, IDisposable
|
||||
{
|
||||
[Parameter]
|
||||
public ConfidenceInfoMode Mode { get; set; } = ConfidenceInfoMode.BUTTON;
|
||||
public PopoverTriggerMode Mode { get; set; } = PopoverTriggerMode.BUTTON;
|
||||
|
||||
[Parameter]
|
||||
public LLMProviders LLMProvider { get; set; }
|
||||
@@ -63,6 +63,8 @@ public partial class ConfidenceInfo : ComponentBase, IMessageBusReceiver, IDispo
|
||||
|
||||
#region Implementation of IMessageBusReceiver
|
||||
|
||||
public string ComponentName => nameof(ConfidenceInfo);
|
||||
|
||||
public Task ProcessMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data)
|
||||
{
|
||||
switch (triggeredEvent)
|
||||
|
||||
@@ -57,6 +57,8 @@ public partial class ConfigurationBase : ComponentBase, IMessageBusReceiver, IDi
|
||||
|
||||
#region Implementation of IMessageBusReceiver
|
||||
|
||||
public string ComponentName => nameof(ConfigurationBase);
|
||||
|
||||
public Task ProcessMessage<TMsg>(ComponentBase? sendingComponent, Event triggeredEvent, TMsg? data)
|
||||
{
|
||||
switch (triggeredEvent)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Settings;
|
||||
|
||||
@@ -47,8 +49,12 @@ public partial class ConfigurationProviderSelection : ComponentBase, IMessageBus
|
||||
|
||||
#endregion
|
||||
|
||||
[SuppressMessage("Usage", "MWAIS0001:Direct access to `Providers` is not allowed")]
|
||||
private IEnumerable<ConfigurationSelectData<string>> FilteredData()
|
||||
{
|
||||
if(this.Component is not Tools.Components.NONE and not Tools.Components.APP_SETTINGS)
|
||||
yield return new("Use app default", string.Empty);
|
||||
|
||||
var minimumLevel = this.SettingsManager.GetMinimumConfidenceLevel(this.Component);
|
||||
foreach (var providerId in this.Data)
|
||||
{
|
||||
@@ -60,6 +66,8 @@ public partial class ConfigurationProviderSelection : ComponentBase, IMessageBus
|
||||
|
||||
#region Implementation of IMessageBusReceiver
|
||||
|
||||
public string ComponentName => nameof(ConfigurationProviderSelection);
|
||||
|
||||
public async Task ProcessMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data)
|
||||
{
|
||||
switch (triggeredEvent)
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using AIStudio.Agents;
|
||||
using AIStudio.Settings;
|
||||
|
||||
namespace AIStudio.Components;
|
||||
|
||||
/// <summary>
|
||||
/// A data structure to combine the data source and the underlying AI decision.
|
||||
/// </summary>
|
||||
public sealed class DataSourceAgentSelected
|
||||
{
|
||||
/// <summary>
|
||||
/// The data source.
|
||||
/// </summary>
|
||||
public required IDataSource DataSource { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The AI decision, which led to the selection of the data source.
|
||||
/// </summary>
|
||||
public required SelectedDataSource AIDecision { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the data source is part of the final selection for the RAG process.
|
||||
/// </summary>
|
||||
public bool Selected { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
@using AIStudio.Settings
|
||||
|
||||
@if (this.SelectionMode is DataSourceSelectionMode.SELECTION_MODE)
|
||||
{
|
||||
<div class="d-flex">
|
||||
<MudTooltip Text="Select the data sources you want to use here." Placement="Placement.Top">
|
||||
@if (this.PopoverTriggerMode is PopoverTriggerMode.ICON)
|
||||
{
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Source" Class="@this.PopoverButtonClasses" OnClick="@(() => this.ToggleDataSourceSelection())"/>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudButton Variant="Variant.Filled" StartIcon="@Icons.Material.Filled.Source" Class="@this.PopoverButtonClasses" OnClick="@(() => this.ToggleDataSourceSelection())">
|
||||
Select data sources
|
||||
</MudButton>
|
||||
}
|
||||
</MudTooltip>
|
||||
|
||||
<MudPopover Open="@this.showDataSourceSelection" AnchorOrigin="Origin.TopLeft" TransformOrigin="Origin.BottomLeft" DropShadow="@true" Class="border-solid border-4 rounded-lg">
|
||||
<MudCard>
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<PreviewPrototype/>
|
||||
<MudText Typo="Typo.h5">Data Source Selection</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Style="min-width: 24em; max-height: 60vh; max-width: 45vw; overflow: auto;">
|
||||
@if (this.waitingForDataSources)
|
||||
{
|
||||
<MudSkeleton Width="30%" Height="42px;"/>
|
||||
<MudSkeleton Width="80%"/>
|
||||
<MudSkeleton Width="100%"/>
|
||||
}
|
||||
else if (this.showDataSourceSelection)
|
||||
{
|
||||
<MudTextSwitch Label="Are data sources enabled?" Value="@this.areDataSourcesEnabled" LabelOn="Yes, I want to use data sources." LabelOff="No, I don't want to use data sources." ValueChanged="@this.EnabledChanged"/>
|
||||
@if (this.areDataSourcesEnabled)
|
||||
{
|
||||
<MudTextSwitch Label="AI-based data source selection" Value="@this.aiBasedSourceSelection" LabelOn="Yes, let the AI decide which data sources are needed." LabelOff="No, I manually decide which data source to use." ValueChanged="@this.AutoModeChanged"/>
|
||||
|
||||
@if (this.SettingsManager.ConfigurationData.AgentRetrievalContextValidation.EnableRetrievalContextValidation)
|
||||
{
|
||||
<MudTextSwitch Label="AI-based data validation" Value="@this.aiBasedValidation" LabelOn="Yes, let the AI validate & filter the retrieved data." LabelOff="No, use all data retrieved from the data sources." ValueChanged="@this.ValidationModeChanged"/>
|
||||
}
|
||||
|
||||
@if (this.aiBasedSourceSelection is false || this.DataSourcesAISelected.Count == 0)
|
||||
{
|
||||
<MudField Label="Available Data Sources" Variant="Variant.Outlined" Class="mb-3" Disabled="@this.aiBasedSourceSelection">
|
||||
<MudList T="IDataSource" SelectionMode="@this.GetListSelectionMode()" @bind-SelectedValues:get="@this.selectedDataSources" @bind-SelectedValues:set="@(x => this.SelectionChanged(x))" Style="max-height: 14em;">
|
||||
@foreach (var source in this.availableDataSources)
|
||||
{
|
||||
<MudListItem Value="@source">
|
||||
@source.Name
|
||||
</MudListItem>
|
||||
}
|
||||
</MudList>
|
||||
</MudField>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudExpansionPanels MultiExpansion="@false" Class="mt-3" Style="max-height: 14em;">
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.TouchApp" HeaderText="Available Data Sources">
|
||||
<MudList T="IDataSource" SelectionMode="MudBlazor.SelectionMode.SingleSelection" SelectedValues="@this.selectedDataSources" Style="max-height: 14em;">
|
||||
@foreach (var source in this.availableDataSources)
|
||||
{
|
||||
<MudListItem Value="@source">
|
||||
@source.Name
|
||||
</MudListItem>
|
||||
}
|
||||
</MudList>
|
||||
</ExpansionPanel>
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Filter" HeaderText="AI-Selected Data Sources">
|
||||
<MudList T="DataSourceAgentSelected" SelectionMode="MudBlazor.SelectionMode.MultiSelection" ReadOnly="@true" SelectedValues="@this.GetSelectedDataSourcesWithAI()" Style="max-height: 14em;">
|
||||
@foreach (var source in this.DataSourcesAISelected)
|
||||
{
|
||||
<MudListItem Value="@source">
|
||||
<ChildContent>
|
||||
<MudText Typo="Typo.body1">
|
||||
@source.DataSource.Name
|
||||
</MudText>
|
||||
|
||||
<MudProgressLinear Color="Color.Info" Min="0" Max="1" Value="@source.AIDecision.Confidence"/>
|
||||
<MudJustifiedText Typo="Typo.body2">
|
||||
@this.GetAIReasoning(source)
|
||||
</MudJustifiedText>
|
||||
</ChildContent>
|
||||
</MudListItem>
|
||||
}
|
||||
</MudList>
|
||||
</ExpansionPanel>
|
||||
</MudExpansionPanels>
|
||||
}
|
||||
}
|
||||
}
|
||||
</MudCardContent>
|
||||
<MudCardActions>
|
||||
<MudButton Variant="Variant.Filled" OnClick="@(() => this.HideDataSourceSelection())">
|
||||
Close
|
||||
</MudButton>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
</MudPopover>
|
||||
</div>
|
||||
}
|
||||
else if (this.SelectionMode is DataSourceSelectionMode.CONFIGURATION_MODE)
|
||||
{
|
||||
<MudPaper Class="pa-3 mb-8 mt-3 border-dashed border rounded-lg">
|
||||
<PreviewPrototype/>
|
||||
<MudText Typo="Typo.h5">Data Source Selection</MudText>
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(this.ConfigurationHeaderMessage))
|
||||
{
|
||||
<MudText Typo="Typo.body1">
|
||||
@this.ConfigurationHeaderMessage
|
||||
</MudText>
|
||||
}
|
||||
|
||||
<MudTextSwitch Label="Are data sources enabled?" Value="@this.areDataSourcesEnabled" LabelOn="Yes, I want to use data sources." LabelOff="No, I don't want to use data sources." ValueChanged="@this.EnabledChanged"/>
|
||||
@if (this.areDataSourcesEnabled)
|
||||
{
|
||||
<MudTextSwitch Label="AI-based data source selection" Value="@this.aiBasedSourceSelection" LabelOn="Yes, let the AI decide which data sources are needed." LabelOff="No, I manually decide which data source to use." ValueChanged="@this.AutoModeChanged"/>
|
||||
<MudTextSwitch Label="AI-based data validation" Value="@this.aiBasedValidation" LabelOn="Yes, let the AI validate & filter the retrieved data." LabelOff="No, use all data retrieved from the data sources." ValueChanged="@this.ValidationModeChanged"/>
|
||||
<MudField Label="Available Data Sources" Variant="Variant.Outlined" Class="mb-3" Disabled="@this.aiBasedSourceSelection">
|
||||
<MudList T="IDataSource" SelectionMode="@this.GetListSelectionMode()" @bind-SelectedValues:get="@this.selectedDataSources" @bind-SelectedValues:set="@(x => this.SelectionChanged(x))">
|
||||
@foreach (var source in this.availableDataSources)
|
||||
{
|
||||
<MudListItem Value="@source">
|
||||
@source.Name
|
||||
</MudListItem>
|
||||
}
|
||||
</MudList>
|
||||
</MudField>
|
||||
}
|
||||
</MudPaper>
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Settings.DataModel;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Components;
|
||||
|
||||
public partial class DataSourceSelection : ComponentBase, IMessageBusReceiver, IDisposable
|
||||
{
|
||||
[Parameter]
|
||||
public DataSourceSelectionMode SelectionMode { get; set; } = DataSourceSelectionMode.SELECTION_MODE;
|
||||
|
||||
[Parameter]
|
||||
public PopoverTriggerMode PopoverTriggerMode { get; set; } = PopoverTriggerMode.BUTTON;
|
||||
|
||||
[Parameter]
|
||||
public string PopoverButtonClasses { get; set; } = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
public required AIStudio.Settings.Provider LLMProvider { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public required DataSourceOptions DataSourceOptions { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<DataSourceOptions> DataSourceOptionsChanged { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public IReadOnlyList<DataSourceAgentSelected> DataSourcesAISelected { get; set; } = [];
|
||||
|
||||
[Parameter]
|
||||
public string ConfigurationHeaderMessage { get; set; } = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
public bool AutoSaveAppSettings { get; set; }
|
||||
|
||||
[Inject]
|
||||
private SettingsManager SettingsManager { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private MessageBus MessageBus { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private DataSourceService DataSourceService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private ILogger<DataSourceSelection> Logger { get; init; } = null!;
|
||||
|
||||
private bool internalChange;
|
||||
private bool showDataSourceSelection;
|
||||
private bool waitingForDataSources = true;
|
||||
private IReadOnlyList<IDataSource> availableDataSources = [];
|
||||
private IReadOnlyCollection<IDataSource> selectedDataSources = [];
|
||||
private bool aiBasedSourceSelection;
|
||||
private bool aiBasedValidation;
|
||||
private bool areDataSourcesEnabled;
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
this.MessageBus.RegisterComponent(this);
|
||||
this.MessageBus.ApplyFilters(this, [], [ Event.COLOR_THEME_CHANGED, Event.RAG_AUTO_DATA_SOURCES_SELECTED ]);
|
||||
|
||||
//
|
||||
// Load the settings:
|
||||
//
|
||||
this.aiBasedSourceSelection = this.DataSourceOptions.AutomaticDataSourceSelection;
|
||||
this.aiBasedValidation = this.DataSourceOptions.AutomaticValidation;
|
||||
this.areDataSourcesEnabled = !this.DataSourceOptions.DisableDataSources;
|
||||
this.waitingForDataSources = this.areDataSourcesEnabled;
|
||||
|
||||
//
|
||||
// Preselect the data sources. Right now, we cannot filter
|
||||
// the data sources. Later, when the component is shown, we
|
||||
// will filter the data sources.
|
||||
//
|
||||
// Right before the preselection would be used to kick off the
|
||||
// RAG process, we will filter the data sources as well.
|
||||
//
|
||||
var preselectedSources = new List<IDataSource>(this.DataSourceOptions.PreselectedDataSourceIds.Count);
|
||||
foreach (var preselectedDataSourceId in this.DataSourceOptions.PreselectedDataSourceIds)
|
||||
{
|
||||
var dataSource = this.SettingsManager.ConfigurationData.DataSources.FirstOrDefault(ds => ds.Id == preselectedDataSourceId);
|
||||
if (dataSource is not null)
|
||||
preselectedSources.Add(dataSource);
|
||||
}
|
||||
|
||||
this.selectedDataSources = preselectedSources;
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (!this.internalChange)
|
||||
{
|
||||
this.aiBasedSourceSelection = this.DataSourceOptions.AutomaticDataSourceSelection;
|
||||
this.aiBasedValidation = this.DataSourceOptions.AutomaticValidation;
|
||||
this.areDataSourcesEnabled = !this.DataSourceOptions.DisableDataSources;
|
||||
}
|
||||
|
||||
switch (this.SelectionMode)
|
||||
{
|
||||
//
|
||||
// In selection mode, we have to load & filter the data sources
|
||||
// when the component is shown:
|
||||
//
|
||||
case DataSourceSelectionMode.SELECTION_MODE:
|
||||
|
||||
//
|
||||
// For external changes, we have to reload & filter
|
||||
// the data sources:
|
||||
//
|
||||
if (this.showDataSourceSelection && !this.internalChange)
|
||||
await this.LoadAndApplyFilters();
|
||||
else
|
||||
this.internalChange = false;
|
||||
|
||||
break;
|
||||
|
||||
//
|
||||
// In configuration mode, we have to load all data sources:
|
||||
//
|
||||
case DataSourceSelectionMode.CONFIGURATION_MODE:
|
||||
this.availableDataSources = this.SettingsManager.ConfigurationData.DataSources;
|
||||
break;
|
||||
}
|
||||
|
||||
await base.OnParametersSetAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private SelectionMode GetListSelectionMode() => this.aiBasedSourceSelection ? MudBlazor.SelectionMode.SingleSelection : MudBlazor.SelectionMode.MultiSelection;
|
||||
|
||||
private IReadOnlyCollection<DataSourceAgentSelected> GetSelectedDataSourcesWithAI() => this.DataSourcesAISelected.Where(n => n.Selected).ToList();
|
||||
|
||||
private string GetAIReasoning(DataSourceAgentSelected source) => $"AI reasoning (confidence {source.AIDecision.Confidence:P0}): {source.AIDecision.Reason}";
|
||||
|
||||
public void ChangeOptionWithoutSaving(DataSourceOptions options, IReadOnlyList<DataSourceAgentSelected>? aiSelectedDataSources = null)
|
||||
{
|
||||
this.DataSourceOptions = options;
|
||||
this.DataSourcesAISelected = aiSelectedDataSources ?? [];
|
||||
|
||||
this.aiBasedSourceSelection = this.DataSourceOptions.AutomaticDataSourceSelection;
|
||||
this.aiBasedValidation = this.DataSourceOptions.AutomaticValidation;
|
||||
this.areDataSourcesEnabled = !this.DataSourceOptions.DisableDataSources;
|
||||
this.selectedDataSources = this.SettingsManager.ConfigurationData.DataSources.Where(ds => this.DataSourceOptions.PreselectedDataSourceIds.Contains(ds.Id)).ToList();
|
||||
this.waitingForDataSources = false;
|
||||
|
||||
//
|
||||
// Remark: We do not apply the filters here. This is done later
|
||||
// when either the parameters are changed or just before the
|
||||
// RAG process is started (outside of this component).
|
||||
//
|
||||
// In fact, when we apply the filters here, multiple calls
|
||||
// to the filter method would be made. We would get conflicts.
|
||||
//
|
||||
}
|
||||
|
||||
public bool IsVisible => this.showDataSourceSelection;
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
this.showDataSourceSelection = false;
|
||||
this.StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task LoadAndApplyFilters()
|
||||
{
|
||||
if(this.DataSourceOptions.DisableDataSources)
|
||||
return;
|
||||
|
||||
this.waitingForDataSources = true;
|
||||
this.StateHasChanged();
|
||||
|
||||
// Load the data sources:
|
||||
var sources = await this.DataSourceService.GetDataSources(this.LLMProvider, this.selectedDataSources);
|
||||
this.availableDataSources = sources.AllowedDataSources;
|
||||
this.selectedDataSources = sources.SelectedDataSources;
|
||||
this.waitingForDataSources = false;
|
||||
this.StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task EnabledChanged(bool state)
|
||||
{
|
||||
this.areDataSourcesEnabled = state;
|
||||
this.DataSourceOptions.DisableDataSources = !this.areDataSourcesEnabled;
|
||||
|
||||
await this.LoadAndApplyFilters();
|
||||
await this.OptionsChanged();
|
||||
this.StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task AutoModeChanged(bool state)
|
||||
{
|
||||
this.aiBasedSourceSelection = state;
|
||||
this.DataSourceOptions.AutomaticDataSourceSelection = this.aiBasedSourceSelection;
|
||||
|
||||
await this.OptionsChanged();
|
||||
}
|
||||
|
||||
private async Task ValidationModeChanged(bool state)
|
||||
{
|
||||
this.aiBasedValidation = state;
|
||||
this.DataSourceOptions.AutomaticValidation = this.aiBasedValidation;
|
||||
|
||||
await this.OptionsChanged();
|
||||
}
|
||||
|
||||
private async Task SelectionChanged(IReadOnlyCollection<IDataSource>? chosenDataSources)
|
||||
{
|
||||
this.selectedDataSources = chosenDataSources ?? [];
|
||||
this.DataSourceOptions.PreselectedDataSourceIds = this.selectedDataSources.Select(ds => ds.Id).ToList();
|
||||
|
||||
await this.OptionsChanged();
|
||||
}
|
||||
|
||||
private async Task OptionsChanged()
|
||||
{
|
||||
this.internalChange = true;
|
||||
|
||||
await this.DataSourceOptionsChanged.InvokeAsync(this.DataSourceOptions);
|
||||
|
||||
if(this.AutoSaveAppSettings)
|
||||
await this.SettingsManager.StoreSettings();
|
||||
}
|
||||
|
||||
private async Task ToggleDataSourceSelection()
|
||||
{
|
||||
this.showDataSourceSelection = !this.showDataSourceSelection;
|
||||
if (this.showDataSourceSelection)
|
||||
await this.LoadAndApplyFilters();
|
||||
}
|
||||
|
||||
private void HideDataSourceSelection() => this.showDataSourceSelection = false;
|
||||
|
||||
#region Implementation of IMessageBusReceiver
|
||||
|
||||
public string ComponentName => nameof(ConfidenceInfo);
|
||||
|
||||
public Task ProcessMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data)
|
||||
{
|
||||
switch (triggeredEvent)
|
||||
{
|
||||
case Event.COLOR_THEME_CHANGED:
|
||||
this.showDataSourceSelection = false;
|
||||
this.StateHasChanged();
|
||||
break;
|
||||
|
||||
case Event.RAG_AUTO_DATA_SOURCES_SELECTED:
|
||||
if(data is IReadOnlyList<DataSourceAgentSelected> aiSelectedDataSources)
|
||||
this.DataSourcesAISelected = aiSelectedDataSources;
|
||||
|
||||
this.StateHasChanged();
|
||||
break;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data)
|
||||
{
|
||||
return Task.FromResult<TResult?>(default);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.MessageBus.Unregister(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace AIStudio.Components;
|
||||
|
||||
public enum DataSourceSelectionMode
|
||||
{
|
||||
/// <summary>
|
||||
/// The user is selecting data sources for, e.g., the chat.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// In this case, we have to filter the data sources based on the
|
||||
/// selected provider and check security requirements.
|
||||
/// </remarks>
|
||||
SELECTION_MODE,
|
||||
|
||||
/// <summary>
|
||||
/// The user is configuring the default data sources, e.g., for the chat.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// In this case, all data sources are available for selection.
|
||||
/// They get filtered later based on the selected provider and
|
||||
/// security requirements.
|
||||
/// </remarks>
|
||||
CONFIGURATION_MODE,
|
||||
}
|
||||
@@ -7,17 +7,17 @@
|
||||
@this.HeaderContent
|
||||
</div>
|
||||
}
|
||||
<div class="flex-auto overflow-auto">
|
||||
<div class="flex-auto overflow-auto mb-3">
|
||||
@this.ChildContent
|
||||
|
||||
<div @ref="@this.AnchorAfterChildContent">
|
||||
<div style="max-height: 0.1em;" @ref="@this.AnchorAfterChildContent">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (this.FooterContent is not null)
|
||||
{
|
||||
<MudPaper Class="pa-3 border-solid border rounded-lg">
|
||||
<MudPaper Class="pa-3 mb-3 border-solid border rounded-lg">
|
||||
@this.FooterContent
|
||||
</MudPaper>
|
||||
}
|
||||
|
||||
@@ -9,14 +9,6 @@ public partial class InnerScrolling : MSGComponentBase
|
||||
[Parameter]
|
||||
public bool FillEntireHorizontalSpace { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set the height of anything above the scrolling content; usually a header.
|
||||
/// What we do is calc(100vh - HeaderHeight). Means, you can use multiple measures like
|
||||
/// 230px - 3em. Default is 3em.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string HeaderHeight { get; set; } = "3em";
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment? HeaderContent { get; set; }
|
||||
|
||||
@@ -34,6 +26,9 @@ public partial class InnerScrolling : MSGComponentBase
|
||||
|
||||
[Parameter]
|
||||
public string? MinWidth { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Style { get; set; } = string.Empty;
|
||||
|
||||
[CascadingParameter]
|
||||
private MainLayout MainLayout { get; set; } = null!;
|
||||
@@ -55,6 +50,8 @@ public partial class InnerScrolling : MSGComponentBase
|
||||
|
||||
#region Overrides of MSGComponentBase
|
||||
|
||||
public override string ComponentName => nameof(InnerScrolling);
|
||||
|
||||
public override Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
|
||||
{
|
||||
switch (triggeredEvent)
|
||||
@@ -74,12 +71,14 @@ public partial class InnerScrolling : MSGComponentBase
|
||||
|
||||
#endregion
|
||||
|
||||
private string MinWidthStyle => string.IsNullOrWhiteSpace(this.MinWidth) ? string.Empty : $"min-width: {this.MinWidth};";
|
||||
private string MinWidthStyle => string.IsNullOrWhiteSpace(this.MinWidth) ? string.Empty : $"min-width: {this.MinWidth}; ";
|
||||
|
||||
private string TerminatedStyles => string.IsNullOrWhiteSpace(this.Style) ? string.Empty : $"{this.Style}; ";
|
||||
|
||||
private string Styles => this.FillEntireHorizontalSpace ? $"height: calc(100vh - {this.HeaderHeight} - {this.MainLayout.AdditionalHeight}); overflow-x: auto; min-width: 0; {this.MinWidthStyle}" : $"height: calc(100vh - {this.HeaderHeight} - {this.MainLayout.AdditionalHeight}); flex-shrink: 0; {this.MinWidthStyle}";
|
||||
|
||||
private string Classes => this.FillEntireHorizontalSpace ? $"{this.Class} d-flex flex-column flex-grow-1" : $"{this.Class} d-flex flex-column";
|
||||
|
||||
private string Styles => $"flex-grow: 1; overflow: hidden; {this.TerminatedStyles}{this.MinWidthStyle}";
|
||||
|
||||
public async Task ScrollToBottom()
|
||||
{
|
||||
await this.AnchorAfterChildContent.ScrollIntoViewAsync(this.JsRuntime);
|
||||
|
||||
@@ -24,6 +24,8 @@ public abstract class MSGComponentBase : ComponentBase, IDisposable, IMessageBus
|
||||
|
||||
#region Implementation of IMessageBusReceiver
|
||||
|
||||
public abstract string ComponentName { get; }
|
||||
|
||||
public Task ProcessMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data)
|
||||
{
|
||||
switch (triggeredEvent)
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
namespace AIStudio.Components;
|
||||
|
||||
public enum ConfidenceInfoMode
|
||||
public enum PopoverTriggerMode
|
||||
{
|
||||
BUTTON,
|
||||
ICON,
|
||||
@@ -1,4 +1,4 @@
|
||||
<MudTooltip Text="You can switch between your profiles here">
|
||||
<MudTooltip Text="You can switch between your profiles here" Placement="Placement.Top">
|
||||
<MudMenu StartIcon="@Icons.Material.Filled.Person4" EndIcon="@Icons.Material.Filled.KeyboardArrowDown" Label="@this.CurrentProfile.Name" Variant="Variant.Filled" Color="Color.Default" Class="@this.MarginClass">
|
||||
@foreach (var profile in this.SettingsManager.ConfigurationData.Profiles.GetAllProfiles())
|
||||
{
|
||||
|
||||
@@ -14,11 +14,14 @@ public partial class ProfileSelection : ComponentBase
|
||||
|
||||
[Parameter]
|
||||
public string MarginLeft { get; set; } = "ml-3";
|
||||
|
||||
[Parameter]
|
||||
public string MarginRight { get; set; } = string.Empty;
|
||||
|
||||
[Inject]
|
||||
private SettingsManager SettingsManager { get; init; } = null!;
|
||||
|
||||
private string MarginClass => $"{this.MarginLeft}";
|
||||
private string MarginClass => $"{this.MarginLeft} {this.MarginRight}";
|
||||
|
||||
private async Task SelectionChanged(Profile profile)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@using AIStudio.Settings
|
||||
|
||||
<MudSelect T="Provider" Value="@this.ProviderSettings" ValueChanged="@this.SelectionChanged" Validation="@this.ValidateProvider" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Apps" Margin="Margin.Dense" Label="Provider" Class="mb-3 rounded-lg" Variant="Variant.Outlined">
|
||||
<MudSelect T="Provider" Value="@this.ProviderSettings" ValueChanged="@this.SelectionChanged" Validation="@this.ValidateProvider" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Apps" Margin="Margin.Dense" Label="Provider" Class="mb-3 rounded-lg" OuterClass="flex-grow-0" Variant="Variant.Outlined">
|
||||
@foreach (var provider in this.GetAvailableProviders())
|
||||
{
|
||||
<MudSelectItem Value="@provider"/>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
using AIStudio.Assistants;
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Settings;
|
||||
@@ -12,24 +14,25 @@ public partial class ProviderSelection : ComponentBase
|
||||
public AssistantBase? AssistantBase { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Settings.Provider ProviderSettings { get; set; }
|
||||
public AIStudio.Settings.Provider ProviderSettings { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<Settings.Provider> ProviderSettingsChanged { get; set; }
|
||||
public EventCallback<AIStudio.Settings.Provider> ProviderSettingsChanged { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<Settings.Provider, string?> ValidateProvider { get; set; } = _ => null;
|
||||
public Func<AIStudio.Settings.Provider, string?> ValidateProvider { get; set; } = _ => null;
|
||||
|
||||
[Inject]
|
||||
private SettingsManager SettingsManager { get; init; } = null!;
|
||||
|
||||
private async Task SelectionChanged(Settings.Provider provider)
|
||||
private async Task SelectionChanged(AIStudio.Settings.Provider provider)
|
||||
{
|
||||
this.ProviderSettings = provider;
|
||||
await this.ProviderSettingsChanged.InvokeAsync(provider);
|
||||
}
|
||||
|
||||
private IEnumerable<Settings.Provider> GetAvailableProviders()
|
||||
[SuppressMessage("Usage", "MWAIS0001:Direct access to `Providers` is not allowed")]
|
||||
private IEnumerable<AIStudio.Settings.Provider> GetAvailableProviders()
|
||||
{
|
||||
var minimumLevel = this.SettingsManager.GetMinimumConfidenceLevel(this.AssistantBase?.Component ?? Tools.Components.NONE);
|
||||
foreach (var provider in this.SettingsManager.ConfigurationData.Providers)
|
||||
|
||||
@@ -24,7 +24,7 @@ public partial class ReadWebContent : ComponentBase
|
||||
public EventCallback<string> ContentChanged { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Settings.Provider ProviderSettings { get; set; }
|
||||
public AIStudio.Settings.Provider ProviderSettings { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool AgentIsRunning { get; set; }
|
||||
@@ -47,7 +47,7 @@ public partial class ReadWebContent : ComponentBase
|
||||
private bool urlIsValid;
|
||||
private bool isProviderValid;
|
||||
|
||||
private Settings.Provider providerSettings;
|
||||
private AIStudio.Settings.Provider providerSettings;
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
|
||||
@@ -59,11 +59,7 @@ public partial class ReadWebContent : ComponentBase
|
||||
if(this.PreselectContentCleanerAgent)
|
||||
this.useContentCleanerAgent = true;
|
||||
|
||||
if (this.SettingsManager.ConfigurationData.TextContentCleaner.PreselectAgentOptions)
|
||||
this.providerSettings = this.SettingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == this.SettingsManager.ConfigurationData.TextContentCleaner.PreselectedAgentProvider);
|
||||
else
|
||||
this.providerSettings = this.ProviderSettings;
|
||||
|
||||
this.ProviderSettings = this.SettingsManager.GetPreselectedProvider(Tools.Components.AGENT_TEXT_CONTENT_CLEANER, this.ProviderSettings.Id, true);
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
Text="@this.Directory"
|
||||
Label="@this.Label"
|
||||
ReadOnly="@true"
|
||||
Validation="@this.Validation"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Folder"
|
||||
UserAttributes="@SPELLCHECK_ATTRIBUTES"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
@@ -21,6 +22,9 @@ public partial class SelectDirectory : ComponentBase
|
||||
[Parameter]
|
||||
public string DirectoryDialogTitle { get; set; } = "Select Directory";
|
||||
|
||||
[Parameter]
|
||||
public Func<string, string?> Validation { get; set; } = _ => null;
|
||||
|
||||
[Inject]
|
||||
private SettingsManager SettingsManager { get; init; } = null!;
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<MudStack Row="@true" Spacing="3" Class="mb-3" StretchItems="StretchItems.None" AlignItems="AlignItems.Center">
|
||||
<MudTextField
|
||||
T="string"
|
||||
Text="@this.File"
|
||||
Label="@this.Label"
|
||||
ReadOnly="@true"
|
||||
Validation="@this.Validation"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.AttachFile"
|
||||
UserAttributes="@SPELLCHECK_ATTRIBUTES"
|
||||
Variant="Variant.Outlined"
|
||||
/>
|
||||
|
||||
<MudButton StartIcon="@Icons.Material.Filled.FolderOpen" Variant="Variant.Outlined" Color="Color.Primary" Disabled="this.Disabled" OnClick="@this.OpenFileDialog">
|
||||
Choose File
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
@@ -0,0 +1,64 @@
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Components;
|
||||
|
||||
public partial class SelectFile : ComponentBase
|
||||
{
|
||||
[Parameter]
|
||||
public string File { get; set; } = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<string> FileChanged { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Label { get; set; } = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
public string FileDialogTitle { get; set; } = "Select File";
|
||||
|
||||
[Parameter]
|
||||
public Func<string, string?> Validation { get; set; } = _ => null;
|
||||
|
||||
[Inject]
|
||||
private SettingsManager SettingsManager { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
public RustService RustService { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
protected ILogger<SelectDirectory> Logger { get; init; } = null!;
|
||||
|
||||
private static readonly Dictionary<string, object?> SPELLCHECK_ATTRIBUTES = new();
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// Configure the spellchecking for the instance name input:
|
||||
this.SettingsManager.InjectSpellchecking(SPELLCHECK_ATTRIBUTES);
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void InternalFileChanged(string file)
|
||||
{
|
||||
this.File = file;
|
||||
this.FileChanged.InvokeAsync(file);
|
||||
}
|
||||
|
||||
private async Task OpenFileDialog()
|
||||
{
|
||||
var response = await this.RustService.SelectFile(this.FileDialogTitle, string.IsNullOrWhiteSpace(this.File) ? null : this.File);
|
||||
this.Logger.LogInformation($"The user selected the file '{response.SelectedFilePath}'.");
|
||||
|
||||
if (!response.UserCancelled)
|
||||
this.InternalFileChanged(response.SelectedFilePath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
@using AIStudio.Settings
|
||||
@inherits SettingsPanelBase
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.CalendarToday" HeaderText="Assistant: Agenda Options">
|
||||
<MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg">
|
||||
<ConfigurationOption OptionDescription="Preselect agenda options?" LabelOn="Agenda options are preselected" LabelOff="No agenda options are preselected" State="@(() => this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.Agenda.PreselectOptions = updatedState)" OptionHelp="When enabled, you can preselect most agenda options. This is might be useful when you need to create similar agendas often."/>
|
||||
<ConfigurationText OptionDescription="Preselect a name?" Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" Icon="@Icons.Material.Filled.Tag" Text="@(() => this.SettingsManager.ConfigurationData.Agenda.PreselectName)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.Agenda.PreselectName = updatedText)" />
|
||||
<ConfigurationText OptionDescription="Preselect a topic?" Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" Icon="@Icons.Material.Filled.EventNote" Text="@(() => this.SettingsManager.ConfigurationData.Agenda.PreselectTopic)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.Agenda.PreselectTopic = updatedText)" />
|
||||
<ConfigurationText OptionDescription="Preselect an objective?" Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" Icon="@Icons.Material.Filled.Flag" Text="@(() => this.SettingsManager.ConfigurationData.Agenda.PreselectObjective)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.Agenda.PreselectObjective = updatedText)" />
|
||||
<ConfigurationText OptionDescription="Preselect a moderator?" Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" Icon="@Icons.Material.Filled.Person3" Text="@(() => this.SettingsManager.ConfigurationData.Agenda.PreselectModerator)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.Agenda.PreselectModerator = updatedText)" />
|
||||
<ConfigurationText OptionDescription="Preselect a duration?" Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" Icon="@Icons.Material.Filled.Schedule" Text="@(() => this.SettingsManager.ConfigurationData.Agenda.PreselectDuration)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.Agenda.PreselectDuration = updatedText)" />
|
||||
<ConfigurationText OptionDescription="Preselect a start time?" Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" Icon="@Icons.Material.Filled.Schedule" Text="@(() => this.SettingsManager.ConfigurationData.Agenda.PreselectStartTime)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.Agenda.PreselectStartTime = updatedText)" />
|
||||
<ConfigurationOption OptionDescription="Preselect whether the participants should get to know each other" Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" LabelOn="Participants should get to know each other" LabelOff="Participants do not need to get to know each other" State="@(() => this.SettingsManager.ConfigurationData.Agenda.PreselectIntroduceParticipants)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.Agenda.PreselectIntroduceParticipants = updatedState)" />
|
||||
<ConfigurationSelect OptionDescription="Preselect the number of participants" Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Agenda.PreselectNumberParticipants)" Data="@ConfigurationSelectDataFactory.GetNumberParticipantsData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Agenda.PreselectNumberParticipants = selectedValue)" OptionHelp="How many participants should be preselected?"/>
|
||||
<ConfigurationOption OptionDescription="Preselect whether the participants should actively involved" Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" LabelOn="Participants should be actively involved" LabelOff="Participants do not need to be actively involved" State="@(() => this.SettingsManager.ConfigurationData.Agenda.PreselectActiveParticipation)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.Agenda.PreselectActiveParticipation = updatedState)" />
|
||||
<ConfigurationOption OptionDescription="Preselect whether the meeting is virtual" Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" LabelOn="Meeting is virtual" LabelOff="Meeting is in person" State="@(() => this.SettingsManager.ConfigurationData.Agenda.PreselectIsMeetingVirtual)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.Agenda.PreselectIsMeetingVirtual = updatedState)" />
|
||||
<ConfigurationText OptionDescription="Preselect a location?" Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" Icon="@Icons.Material.Filled.MyLocation" Text="@(() => this.SettingsManager.ConfigurationData.Agenda.PreselectLocation)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.Agenda.PreselectLocation = updatedText)" />
|
||||
<ConfigurationOption OptionDescription="Preselect whether there is a joint dinner" Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" LabelOn="There is a joint dinner" LabelOff="There is no joint dinner" State="@(() => this.SettingsManager.ConfigurationData.Agenda.PreselectJointDinner)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.Agenda.PreselectJointDinner = updatedState)" />
|
||||
<ConfigurationOption OptionDescription="Preselect whether there is a social event" Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" LabelOn="There is a social event" LabelOff="There is no social event" State="@(() => this.SettingsManager.ConfigurationData.Agenda.PreselectSocialActivity)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.Agenda.PreselectSocialActivity = updatedState)" />
|
||||
<ConfigurationOption OptionDescription="Preselect whether participants needs to arrive and depart" Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" LabelOn="Participants need to arrive and depart" LabelOff="Participants do not need to arrive and depart" State="@(() => this.SettingsManager.ConfigurationData.Agenda.PreselectArriveAndDepart)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.Agenda.PreselectArriveAndDepart = updatedState)" />
|
||||
<ConfigurationSlider T="int" OptionDescription="Preselect the approx. lunch time" Min="30" Max="120" Step="5" Unit="minutes" Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" Value="@(() => this.SettingsManager.ConfigurationData.Agenda.PreselectLunchTime)" ValueUpdate="@(updatedValue => this.SettingsManager.ConfigurationData.Agenda.PreselectLunchTime = updatedValue)" />
|
||||
<ConfigurationSlider T="int" OptionDescription="Preselect the approx. break time" Min="10" Max="60" Step="5" Unit="minutes" Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" Value="@(() => this.SettingsManager.ConfigurationData.Agenda.PreselectBreakTime)" ValueUpdate="@(updatedValue => this.SettingsManager.ConfigurationData.Agenda.PreselectBreakTime = updatedValue)" />
|
||||
<ConfigurationSelect OptionDescription="Preselect the agenda language" Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Agenda.PreselectedTargetLanguage)" Data="@ConfigurationSelectDataFactory.GetCommonLanguagesTranslationData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Agenda.PreselectedTargetLanguage = selectedValue)" OptionHelp="Which agenda language should be preselected?"/>
|
||||
@if (this.SettingsManager.ConfigurationData.Agenda.PreselectedTargetLanguage is CommonLanguages.OTHER)
|
||||
{
|
||||
<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.AvailableLLMProvidersFunc()" 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>
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace AIStudio.Components.Settings;
|
||||
|
||||
public partial class SettingsPanelAgenda : SettingsPanelBase;
|
||||
@@ -0,0 +1,12 @@
|
||||
@inherits SettingsPanelBase
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.TextFields" HeaderText="Agent: Text Content Cleaner Options">
|
||||
<MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg">
|
||||
<MudText Typo="Typo.body1" Class="mb-3">
|
||||
Use Case: this agent is used to clean up text content. It extracts the main content, removes advertisements and other irrelevant things,
|
||||
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.AvailableLLMProvidersFunc()" Disabled="@(() => !this.SettingsManager.ConfigurationData.TextContentCleaner.PreselectAgentOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.TextContentCleaner.PreselectedAgentProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.TextContentCleaner.PreselectedAgentProvider = selectedValue)"/>
|
||||
</MudPaper>
|
||||
</ExpansionPanel>
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace AIStudio.Components.Settings;
|
||||
|
||||
public partial class SettingsPanelAgentContentCleaner : SettingsPanelBase;
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
@inherits SettingsPanelBase
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.SelectAll" HeaderText="Agent: Data Source Selection Options">
|
||||
<MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg">
|
||||
<MudText Typo="Typo.body1" Class="mb-3">
|
||||
Use Case: this agent is used to select the appropriate data sources for the current prompt.
|
||||
</MudText>
|
||||
<ConfigurationOption OptionDescription="Preselect data source selection options?" LabelOn="Options are preselected" LabelOff="No options are preselected" State="@(() => this.SettingsManager.ConfigurationData.AgentDataSourceSelection.PreselectAgentOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.AgentDataSourceSelection.PreselectAgentOptions = updatedState)" OptionHelp="When enabled, you can preselect some agent options. This is might be useful when you prefer a LLM."/>
|
||||
<ConfigurationProviderSelection Data="@this.AvailableLLMProvidersFunc()" Disabled="@(() => !this.SettingsManager.ConfigurationData.AgentDataSourceSelection.PreselectAgentOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.AgentDataSourceSelection.PreselectedAgentProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.AgentDataSourceSelection.PreselectedAgentProvider = selectedValue)"/>
|
||||
</MudPaper>
|
||||
</ExpansionPanel>
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
namespace AIStudio.Components.Settings;
|
||||
|
||||
public partial class SettingsPanelAgentDataSourceSelection : SettingsPanelBase;
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
@inherits SettingsPanelBase
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Assessment" HeaderText="Agent: Retrieval Context Validation Options">
|
||||
<MudText Typo="Typo.body1" Class="mb-3">
|
||||
Use Case: this agent is used to validate any retrieval context of any retrieval process. Perhaps there are many of these
|
||||
retrieval contexts and you want to validate them all. Therefore, you might want to use a cheap and fast LLM for this
|
||||
job. When using a local or self-hosted LLM, look for a small (e.g. 3B) and fast model.
|
||||
</MudText>
|
||||
<ConfigurationOption OptionDescription="Enable the retrieval context validation agent?" LabelOn="The validation agent is enabled" LabelOff="No validation is performed" State="@(() => this.SettingsManager.ConfigurationData.AgentRetrievalContextValidation.EnableRetrievalContextValidation)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.AgentRetrievalContextValidation.EnableRetrievalContextValidation = updatedState)" OptionHelp="When enabled, the retrieval context validation agent will check each retrieval context of any retrieval process, whether a context makes sense for the given prompt."/>
|
||||
@if (this.SettingsManager.ConfigurationData.AgentRetrievalContextValidation.EnableRetrievalContextValidation)
|
||||
{
|
||||
<MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg">
|
||||
<ConfigurationOption OptionDescription="Preselect retrieval context validation options?" LabelOn="Options are preselected" LabelOff="No options are preselected" State="@(() => this.SettingsManager.ConfigurationData.AgentRetrievalContextValidation.PreselectAgentOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.AgentRetrievalContextValidation.PreselectAgentOptions = updatedState)" OptionHelp="When enabled, you can preselect some agent options. This is might be useful when you prefer a LLM."/>
|
||||
<ConfigurationSlider T="int" OptionDescription="How many validation agents should work simultaneously?" Min="1" Max="100" Step="1" Unit="agents" Value="@(() => this.SettingsManager.ConfigurationData.AgentRetrievalContextValidation.NumParallelValidations)" ValueUpdate="@(updatedValue => this.SettingsManager.ConfigurationData.AgentRetrievalContextValidation.NumParallelValidations = updatedValue)" OptionHelp="More active agents also mean that a corresponding number of requests are made simultaneously. Some providers limit the number of requests per minute. When you are unsure, choose a low setting between 1 to 6 agents."/>
|
||||
<ConfigurationProviderSelection Data="@this.AvailableLLMProvidersFunc()" Disabled="@(() => !this.SettingsManager.ConfigurationData.AgentRetrievalContextValidation.PreselectAgentOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.AgentRetrievalContextValidation.PreselectedAgentProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.AgentRetrievalContextValidation.PreselectedAgentProvider = selectedValue)"/>
|
||||
</MudPaper>
|
||||
}
|
||||
</ExpansionPanel>
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
namespace AIStudio.Components.Settings;
|
||||
|
||||
public partial class SettingsPanelAgentRetrievalContextValidation : SettingsPanelBase;
|
||||
@@ -0,0 +1,24 @@
|
||||
@using AIStudio.Settings
|
||||
@using AIStudio.Settings.DataModel
|
||||
@inherits SettingsPanelBase
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Apps" HeaderText="App Options">
|
||||
<ConfigurationSelect OptionDescription="Color theme" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.PreferredTheme)" Data="@ConfigurationSelectDataFactory.GetThemesData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.PreferredTheme = selectedValue)" OptionHelp="Choose the color theme that best suits for you."/>
|
||||
<ConfigurationOption OptionDescription="Save energy?" LabelOn="Energy saving is enabled" LabelOff="Energy saving is disabled" State="@(() => this.SettingsManager.ConfigurationData.App.IsSavingEnergy)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.App.IsSavingEnergy = updatedState)" OptionHelp="When enabled, streamed content from the AI is updated once every third second. When disabled, streamed content will be updated as soon as it is available."/>
|
||||
<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."/>
|
||||
<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 Component="Components.APP_SETTINGS" Data="@this.AvailableLLMProvidersFunc()" 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>
|
||||
@@ -0,0 +1,12 @@
|
||||
using AIStudio.Settings.DataModel;
|
||||
|
||||
namespace AIStudio.Components.Settings;
|
||||
|
||||
public partial class SettingsPanelApp : SettingsPanelBase
|
||||
{
|
||||
private void UpdatePreviewFeatures(PreviewVisibility previewVisibility)
|
||||
{
|
||||
this.SettingsManager.ConfigurationData.App.PreviewVisibility = previewVisibility;
|
||||
this.SettingsManager.ConfigurationData.App.EnabledPreviewFeatures = previewVisibility.FilterPreviewFeatures(this.SettingsManager.ConfigurationData.App.EnabledPreviewFeatures);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
@using AIStudio.Settings
|
||||
@using AIStudio.Settings.DataModel
|
||||
@inherits SettingsPanelBase
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Psychology" HeaderText="Assistant: Bias of the Day">
|
||||
|
||||
<ConfigurationOption OptionDescription="Restrict to one bias a day?" LabelOn="Yes, you can only retrieve one bias per day" LabelOff="No restriction. You can retrieve as many biases as you want per day." State="@(() => this.SettingsManager.ConfigurationData.BiasOfTheDay.RestrictOneBiasPerDay)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.BiasOfTheDay.RestrictOneBiasPerDay = updatedState)"/>
|
||||
|
||||
<MudField Label="Statistics" Variant="Variant.Outlined" Class="mb-2">
|
||||
<MudText Typo="Typo.body1">
|
||||
You have learned about @this.SettingsManager.ConfigurationData.BiasOfTheDay.UsedBias.Count out of @BiasCatalog.ALL_BIAS.Count biases.
|
||||
</MudText>
|
||||
<MudButton Size="Size.Small" Variant="Variant.Filled" StartIcon="@Icons.Material.Filled.Restore" Color="Color.Error" OnClick="@(() => this.ResetBiasOfTheDayHistory())">
|
||||
Reset
|
||||
</MudButton>
|
||||
</MudField>
|
||||
|
||||
<MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg">
|
||||
<ConfigurationOption OptionDescription="Preselect options?" LabelOn="Options are preselected" LabelOff="No options are preselected" State="@(() => this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectOptions = updatedState)" OptionHelp="When enabled, you can preselect options. This is might be useful when you prefer a specific language or LLM model."/>
|
||||
<ConfigurationSelect OptionDescription="Preselect the language" Disabled="@(() => !this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectedTargetLanguage)" Data="@ConfigurationSelectDataFactory.GetCommonLanguagesOptionalData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectedTargetLanguage = selectedValue)" OptionHelp="Which language should be preselected?"/>
|
||||
@if (this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectedTargetLanguage is CommonLanguages.OTHER)
|
||||
{
|
||||
<ConfigurationText OptionDescription="Preselect another language" Disabled="@(() => !this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectOptions)" Icon="@Icons.Material.Filled.Translate" Text="@(() => this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectedOtherLanguage)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectedOtherLanguage = updatedText)"/>
|
||||
}
|
||||
<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.AvailableLLMProvidersFunc()" Disabled="@(() => !this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectedProvider = selectedValue)"/>
|
||||
</MudPaper>
|
||||
</ExpansionPanel>
|
||||
@@ -0,0 +1,27 @@
|
||||
using AIStudio.Dialogs;
|
||||
|
||||
using DialogOptions = AIStudio.Dialogs.DialogOptions;
|
||||
|
||||
namespace AIStudio.Components.Settings;
|
||||
|
||||
public partial class SettingsPanelAssistantBias : SettingsPanelBase
|
||||
{
|
||||
private async Task ResetBiasOfTheDayHistory()
|
||||
{
|
||||
var dialogParameters = new DialogParameters
|
||||
{
|
||||
{ "Message", "Are you sure you want to reset your bias-of-the-day statistics? The system will no longer remember which biases you already know. As a result, biases you are already familiar with may be addressed again." },
|
||||
};
|
||||
|
||||
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Reset your bias-of-the-day statistics", dialogParameters, DialogOptions.FULLSCREEN);
|
||||
var dialogResult = await dialogReference.Result;
|
||||
if (dialogResult is null || dialogResult.Canceled)
|
||||
return;
|
||||
|
||||
this.SettingsManager.ConfigurationData.BiasOfTheDay.UsedBias.Clear();
|
||||
this.SettingsManager.ConfigurationData.BiasOfTheDay.DateLastBiasDrawn = DateOnly.MinValue;
|
||||
await this.SettingsManager.StoreSettings();
|
||||
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Components.Settings;
|
||||
|
||||
public abstract class SettingsPanelBase : ComponentBase
|
||||
{
|
||||
[Parameter]
|
||||
public Func<IReadOnlyList<ConfigurationSelectData<string>>> AvailableLLMProvidersFunc { get; set; } = () => [];
|
||||
|
||||
[Inject]
|
||||
protected SettingsManager SettingsManager { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
protected IDialogService DialogService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
protected MessageBus MessageBus { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
protected RustService RustService { get; init; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
@using AIStudio.Settings
|
||||
@using AIStudio.Settings.DataModel
|
||||
@inherits SettingsPanelBase
|
||||
|
||||
<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 Component="Components.CHAT" Data="@this.AvailableLLMProvidersFunc()" 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>
|
||||
|
||||
@if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager))
|
||||
{
|
||||
<DataSourceSelection SelectionMode="DataSourceSelectionMode.CONFIGURATION_MODE" AutoSaveAppSettings="@true" @bind-DataSourceOptions="@this.SettingsManager.ConfigurationData.Chat.PreselectedDataSourceOptions" ConfigurationHeaderMessage="You can set default data sources and options for new chats. You can change these settings later for each individual chat."/>
|
||||
<ConfigurationSelect OptionDescription="Apply default data source option when sending assistant results to chat" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Chat.SendToChatDataSourceBehavior)" Data="@ConfigurationSelectDataFactory.GetSendToChatDataSourceBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Chat.SendToChatDataSourceBehavior = selectedValue)" OptionHelp="Do you want to apply the default data source options when sending assistant results to chat?"/>
|
||||
}
|
||||
</ExpansionPanel>
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace AIStudio.Components.Settings;
|
||||
|
||||
public partial class SettingsPanelChat : SettingsPanelBase;
|
||||
@@ -0,0 +1,18 @@
|
||||
@using AIStudio.Assistants.Coding
|
||||
@using AIStudio.Settings
|
||||
@inherits SettingsPanelBase
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Code" HeaderText="Assistant: Coding Options">
|
||||
<MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg">
|
||||
<ConfigurationOption OptionDescription="Preselect coding options?" LabelOn="Coding options are preselected" LabelOff="No coding options are preselected" State="@(() => this.SettingsManager.ConfigurationData.Coding.PreselectOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.Coding.PreselectOptions = updatedState)" OptionHelp="When enabled, you can preselect the coding options. This is might be useful when you prefer a specific programming language or LLM model."/>
|
||||
<ConfigurationOption OptionDescription="Preselect compiler messages?" Disabled="@(() => !this.SettingsManager.ConfigurationData.Coding.PreselectOptions)" LabelOn="Compiler messages are preselected" LabelOff="Compiler messages are not preselected" State="@(() => this.SettingsManager.ConfigurationData.Coding.PreselectCompilerMessages)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.Coding.PreselectCompilerMessages = updatedState)" />
|
||||
<ConfigurationSelect OptionDescription="Preselect a programming language" Disabled="@(() => !this.SettingsManager.ConfigurationData.Coding.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Coding.PreselectedProgrammingLanguage)" Data="@ConfigurationSelectDataFactory.GetCommonCodingLanguagesData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Coding.PreselectedProgrammingLanguage = selectedValue)" OptionHelp="Which programming language should be preselected for added contexts?"/>
|
||||
@if (this.SettingsManager.ConfigurationData.Coding.PreselectedProgrammingLanguage is CommonCodingLanguages.OTHER)
|
||||
{
|
||||
<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.AvailableLLMProvidersFunc()" 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>
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace AIStudio.Components.Settings;
|
||||
|
||||
public partial class SettingsPanelCoding : SettingsPanelBase;
|
||||
@@ -0,0 +1,61 @@
|
||||
@using AIStudio.Settings.DataModel
|
||||
@inherits SettingsPanelBase
|
||||
|
||||
@if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager))
|
||||
{
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.IntegrationInstructions" HeaderText="Configure Data Sources">
|
||||
<PreviewPrototype/>
|
||||
<MudText Typo="Typo.h4" Class="mb-3">
|
||||
Configured Data Sources
|
||||
</MudText>
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
|
||||
You might configure different data sources. A data source can include one file, all files
|
||||
in a directory, or data from your company. Later, you can incorporate these data sources
|
||||
as needed when the AI requires this data to complete a certain task.
|
||||
</MudJustifiedText>
|
||||
|
||||
<MudTable Items="@this.SettingsManager.ConfigurationData.DataSources" Hover="@true" Class="border-dashed border rounded-lg">
|
||||
<ColGroup>
|
||||
<col style="width: 3em;"/>
|
||||
<col/>
|
||||
<col style="width: 12em;"/>
|
||||
<col style="width: 12em;"/>
|
||||
<col style="width: 40em;"/>
|
||||
</ColGroup>
|
||||
<HeaderContent>
|
||||
<MudTh>#</MudTh>
|
||||
<MudTh>Name</MudTh>
|
||||
<MudTh>Type</MudTh>
|
||||
<MudTh>Embedding</MudTh>
|
||||
<MudTh Style="text-align: left;">Actions</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd>@context.Num</MudTd>
|
||||
<MudTd>@context.Name</MudTd>
|
||||
<MudTd>@context.Type.GetDisplayName()</MudTd>
|
||||
<MudTd>@this.GetEmbeddingName(context)</MudTd>
|
||||
|
||||
<MudTd Style="text-align: left;">
|
||||
<MudIconButton Variant="Variant.Filled" Color="Color.Info" Icon="@Icons.Material.Filled.Info" Class="ma-2" OnClick="() => this.ShowInformation(context)"/>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Edit" Class="ma-2" OnClick="() => this.EditDataSource(context)">
|
||||
Edit
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Error" StartIcon="@Icons.Material.Filled.Delete" Class="ma-2" OnClick="() => this.DeleteDataSource(context)">
|
||||
Delete
|
||||
</MudButton>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
|
||||
@if (this.SettingsManager.ConfigurationData.DataSources.Count == 0)
|
||||
{
|
||||
<MudText Typo="Typo.h6" Class="mt-3">No data sources configured yet.</MudText>
|
||||
}
|
||||
|
||||
<MudMenu EndIcon="@Icons.Material.Filled.KeyboardArrowDown" Label="Add Data Source" Color="Color.Primary" Variant="Variant.Filled" AnchorOrigin="Origin.CenterCenter" TransformOrigin="Origin.TopLeft" Class="mt-3 mb-6">
|
||||
<MudMenuItem OnClick="() => this.AddDataSource(DataSourceType.ERI_V1)">External Data (ERI-Server v1)</MudMenuItem>
|
||||
<MudMenuItem OnClick="() => this.AddDataSource(DataSourceType.LOCAL_DIRECTORY)">Local Directory</MudMenuItem>
|
||||
<MudMenuItem OnClick="() => this.AddDataSource(DataSourceType.LOCAL_FILE)">Local File</MudMenuItem>
|
||||
</MudMenu>
|
||||
</ExpansionPanel>
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
using AIStudio.Dialogs;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Settings.DataModel;
|
||||
using AIStudio.Tools.ERIClient.DataModel;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
using DialogOptions = AIStudio.Dialogs.DialogOptions;
|
||||
|
||||
namespace AIStudio.Components.Settings;
|
||||
|
||||
public partial class SettingsPanelDataSources : SettingsPanelBase
|
||||
{
|
||||
[Parameter]
|
||||
public List<ConfigurationSelectData<string>> AvailableDataSources { get; set; } = new();
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<List<ConfigurationSelectData<string>>> AvailableDataSourcesChanged { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<IReadOnlyList<ConfigurationSelectData<string>>> AvailableEmbeddingsFunc { get; set; } = () => [];
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await this.UpdateDataSources();
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private string GetEmbeddingName(IDataSource dataSource)
|
||||
{
|
||||
if(dataSource is IInternalDataSource internalDataSource)
|
||||
{
|
||||
var matchedEmbedding = this.SettingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id == internalDataSource.EmbeddingId);
|
||||
if(matchedEmbedding == default)
|
||||
return "No valid embedding";
|
||||
|
||||
return matchedEmbedding.Name;
|
||||
}
|
||||
|
||||
if(dataSource is IExternalDataSource)
|
||||
return "External (ERI)";
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
private async Task AddDataSource(DataSourceType type)
|
||||
{
|
||||
IDataSource? addedDataSource = null;
|
||||
switch (type)
|
||||
{
|
||||
case DataSourceType.LOCAL_FILE:
|
||||
var localFileDialogParameters = new DialogParameters<DataSourceLocalFileDialog>
|
||||
{
|
||||
{ x => x.IsEditing, false },
|
||||
{ x => x.AvailableEmbeddings, this.AvailableEmbeddingsFunc() }
|
||||
};
|
||||
|
||||
var localFileDialogReference = await this.DialogService.ShowAsync<DataSourceLocalFileDialog>("Add Local File as Data Source", localFileDialogParameters, DialogOptions.FULLSCREEN);
|
||||
var localFileDialogResult = await localFileDialogReference.Result;
|
||||
if (localFileDialogResult is null || localFileDialogResult.Canceled)
|
||||
return;
|
||||
|
||||
var localFile = (DataSourceLocalFile)localFileDialogResult.Data!;
|
||||
localFile = localFile with { Num = this.SettingsManager.ConfigurationData.NextDataSourceNum++ };
|
||||
addedDataSource = localFile;
|
||||
break;
|
||||
|
||||
case DataSourceType.LOCAL_DIRECTORY:
|
||||
var localDirectoryDialogParameters = new DialogParameters<DataSourceLocalDirectoryDialog>
|
||||
{
|
||||
{ x => x.IsEditing, false },
|
||||
{ x => x.AvailableEmbeddings, this.AvailableEmbeddingsFunc() }
|
||||
};
|
||||
|
||||
var localDirectoryDialogReference = await this.DialogService.ShowAsync<DataSourceLocalDirectoryDialog>("Add Local Directory as Data Source", localDirectoryDialogParameters, DialogOptions.FULLSCREEN);
|
||||
var localDirectoryDialogResult = await localDirectoryDialogReference.Result;
|
||||
if (localDirectoryDialogResult is null || localDirectoryDialogResult.Canceled)
|
||||
return;
|
||||
|
||||
var localDirectory = (DataSourceLocalDirectory)localDirectoryDialogResult.Data!;
|
||||
localDirectory = localDirectory with { Num = this.SettingsManager.ConfigurationData.NextDataSourceNum++ };
|
||||
addedDataSource = localDirectory;
|
||||
break;
|
||||
|
||||
case DataSourceType.ERI_V1:
|
||||
var eriDialogParameters = new DialogParameters<DataSourceERI_V1Dialog>
|
||||
{
|
||||
{ x => x.IsEditing, false },
|
||||
};
|
||||
|
||||
var eriDialogReference = await this.DialogService.ShowAsync<DataSourceERI_V1Dialog>("Add ERI v1 Data Source", eriDialogParameters, DialogOptions.FULLSCREEN);
|
||||
var eriDialogResult = await eriDialogReference.Result;
|
||||
if (eriDialogResult is null || eriDialogResult.Canceled)
|
||||
return;
|
||||
|
||||
var eriDataSource = (DataSourceERI_V1)eriDialogResult.Data!;
|
||||
eriDataSource = eriDataSource with { Num = this.SettingsManager.ConfigurationData.NextDataSourceNum++ };
|
||||
addedDataSource = eriDataSource;
|
||||
break;
|
||||
}
|
||||
|
||||
if(addedDataSource is null)
|
||||
return;
|
||||
|
||||
this.SettingsManager.ConfigurationData.DataSources.Add(addedDataSource);
|
||||
await this.UpdateDataSources();
|
||||
await this.SettingsManager.StoreSettings();
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
|
||||
private async Task EditDataSource(IDataSource dataSource)
|
||||
{
|
||||
IDataSource? editedDataSource = null;
|
||||
switch (dataSource)
|
||||
{
|
||||
case DataSourceLocalFile localFile:
|
||||
var localFileDialogParameters = new DialogParameters<DataSourceLocalFileDialog>
|
||||
{
|
||||
{ x => x.IsEditing, true },
|
||||
{ x => x.DataSource, localFile },
|
||||
{ x => x.AvailableEmbeddings, this.AvailableEmbeddingsFunc() }
|
||||
};
|
||||
|
||||
var localFileDialogReference = await this.DialogService.ShowAsync<DataSourceLocalFileDialog>("Edit Local File Data Source", localFileDialogParameters, DialogOptions.FULLSCREEN);
|
||||
var localFileDialogResult = await localFileDialogReference.Result;
|
||||
if (localFileDialogResult is null || localFileDialogResult.Canceled)
|
||||
return;
|
||||
|
||||
editedDataSource = (DataSourceLocalFile)localFileDialogResult.Data!;
|
||||
break;
|
||||
|
||||
case DataSourceLocalDirectory localDirectory:
|
||||
var localDirectoryDialogParameters = new DialogParameters<DataSourceLocalDirectoryDialog>
|
||||
{
|
||||
{ x => x.IsEditing, true },
|
||||
{ x => x.DataSource, localDirectory },
|
||||
{ x => x.AvailableEmbeddings, this.AvailableEmbeddingsFunc() }
|
||||
};
|
||||
|
||||
var localDirectoryDialogReference = await this.DialogService.ShowAsync<DataSourceLocalDirectoryDialog>("Edit Local Directory Data Source", localDirectoryDialogParameters, DialogOptions.FULLSCREEN);
|
||||
var localDirectoryDialogResult = await localDirectoryDialogReference.Result;
|
||||
if (localDirectoryDialogResult is null || localDirectoryDialogResult.Canceled)
|
||||
return;
|
||||
|
||||
editedDataSource = (DataSourceLocalDirectory)localDirectoryDialogResult.Data!;
|
||||
break;
|
||||
|
||||
case DataSourceERI_V1 eriDataSource:
|
||||
var eriDialogParameters = new DialogParameters<DataSourceERI_V1Dialog>
|
||||
{
|
||||
{ x => x.IsEditing, true },
|
||||
{ x => x.DataSource, eriDataSource },
|
||||
};
|
||||
|
||||
var eriDialogReference = await this.DialogService.ShowAsync<DataSourceERI_V1Dialog>("Edit ERI v1 Data Source", eriDialogParameters, DialogOptions.FULLSCREEN);
|
||||
var eriDialogResult = await eriDialogReference.Result;
|
||||
if (eriDialogResult is null || eriDialogResult.Canceled)
|
||||
return;
|
||||
|
||||
editedDataSource = (DataSourceERI_V1)eriDialogResult.Data!;
|
||||
break;
|
||||
}
|
||||
|
||||
if(editedDataSource is null)
|
||||
return;
|
||||
|
||||
this.SettingsManager.ConfigurationData.DataSources[this.SettingsManager.ConfigurationData.DataSources.IndexOf(dataSource)] = editedDataSource;
|
||||
|
||||
await this.UpdateDataSources();
|
||||
await this.SettingsManager.StoreSettings();
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
|
||||
private async Task DeleteDataSource(IDataSource dataSource)
|
||||
{
|
||||
var dialogParameters = new DialogParameters
|
||||
{
|
||||
{ "Message", $"Are you sure you want to delete the data source '{dataSource.Name}' of type {dataSource.Type.GetDisplayName()}?" },
|
||||
};
|
||||
|
||||
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Delete Data Source", dialogParameters, DialogOptions.FULLSCREEN);
|
||||
var dialogResult = await dialogReference.Result;
|
||||
if (dialogResult is null || dialogResult.Canceled)
|
||||
return;
|
||||
|
||||
var applyChanges = dataSource is IInternalDataSource;
|
||||
|
||||
// External data sources may need a secret for authentication:
|
||||
if (dataSource is IExternalDataSource externalDataSource)
|
||||
{
|
||||
// When the auth method is NONE or KERBEROS, we don't need to delete a secret.
|
||||
// In the case of KERBEROS, we don't store the Kerberos ticket in the secret store.
|
||||
if(dataSource is IERIDataSource { AuthMethod: AuthMethod.NONE or AuthMethod.KERBEROS })
|
||||
applyChanges = true;
|
||||
|
||||
// All other auth methods require a secret, which we need to delete now:
|
||||
else
|
||||
{
|
||||
var deleteSecretResponse = await this.RustService.DeleteSecret(externalDataSource);
|
||||
if (deleteSecretResponse.Success)
|
||||
applyChanges = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(applyChanges)
|
||||
{
|
||||
this.SettingsManager.ConfigurationData.DataSources.Remove(dataSource);
|
||||
await this.SettingsManager.StoreSettings();
|
||||
await this.UpdateDataSources();
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ShowInformation(IDataSource dataSource)
|
||||
{
|
||||
switch (dataSource)
|
||||
{
|
||||
case DataSourceLocalFile localFile:
|
||||
var localFileDialogParameters = new DialogParameters<DataSourceLocalFileInfoDialog>
|
||||
{
|
||||
{ x => x.DataSource, localFile },
|
||||
};
|
||||
|
||||
await this.DialogService.ShowAsync<DataSourceLocalFileInfoDialog>("Local File Data Source Information", localFileDialogParameters, DialogOptions.FULLSCREEN);
|
||||
break;
|
||||
|
||||
case DataSourceLocalDirectory localDirectory:
|
||||
var localDirectoryDialogParameters = new DialogParameters<DataSourceLocalDirectoryInfoDialog>
|
||||
{
|
||||
{ x => x.DataSource, localDirectory },
|
||||
};
|
||||
|
||||
await this.DialogService.ShowAsync<DataSourceLocalDirectoryInfoDialog>("Local Directory Data Source Information", localDirectoryDialogParameters, DialogOptions.FULLSCREEN);
|
||||
break;
|
||||
|
||||
case DataSourceERI_V1 eriV1DataSource:
|
||||
var eriV1DialogParameters = new DialogParameters<DataSourceERI_V1InfoDialog>
|
||||
{
|
||||
{ x => x.DataSource, eriV1DataSource },
|
||||
};
|
||||
|
||||
await this.DialogService.ShowAsync<DataSourceERI_V1InfoDialog>("ERI v1 Data Source Information", eriV1DialogParameters, DialogOptions.FULLSCREEN);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateDataSources()
|
||||
{
|
||||
this.AvailableDataSources.Clear();
|
||||
foreach (var dataSource in this.SettingsManager.ConfigurationData.DataSources)
|
||||
this.AvailableDataSources.Add(new (dataSource.Name, dataSource.Id));
|
||||
|
||||
await this.AvailableDataSourcesChanged.InvokeAsync(this.AvailableDataSources);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
@using AIStudio.Settings
|
||||
@inherits SettingsPanelBase
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.PrivateConnectivity" HeaderText="Assistant: ERI Server Options">
|
||||
<MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg">
|
||||
<ConfigurationOption OptionDescription="Preselect ERI server options?" LabelOn="ERI server options are preselected" LabelOff="No ERI server options are preselected" State="@(() => this.SettingsManager.ConfigurationData.ERI.PreselectOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.ERI.PreselectOptions = updatedState)" OptionHelp="When enabled, you can preselect some ERI server options."/>
|
||||
<ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.ERI.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.ERI.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.ERI.MinimumProviderConfidence = selectedValue)"/>
|
||||
<ConfigurationSelect OptionDescription="Preselect one of your profiles?" Disabled="@(() => !this.SettingsManager.ConfigurationData.ERI.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.ERI.PreselectedProfile)" Data="@ConfigurationSelectDataFactory.GetProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.ERI.PreselectedProfile = selectedValue)" OptionHelp="Would you like to preselect one of your profiles?"/>
|
||||
|
||||
<MudText Typo="Typo.body1" Class="mb-3">
|
||||
Most ERI server options can be customized and saved directly in the ERI server assistant.
|
||||
For this, the ERI server assistant has an auto-save function.
|
||||
</MudText>
|
||||
|
||||
<MudButton Size="Size.Large" Variant="Variant.Filled" StartIcon="@Icons.Material.Filled.PrivateConnectivity" Color="Color.Default" Href="@Routes.ASSISTANT_ERI">
|
||||
Switch to ERI server assistant
|
||||
</MudButton>
|
||||
</MudPaper>
|
||||
</ExpansionPanel>
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace AIStudio.Components.Settings;
|
||||
|
||||
public partial class SettingsPanelERIServer : SettingsPanelBase;
|
||||
@@ -0,0 +1,70 @@
|
||||
@using AIStudio.Provider
|
||||
@using AIStudio.Settings.DataModel
|
||||
@inherits SettingsPanelBase
|
||||
|
||||
@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>
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
using AIStudio.Dialogs;
|
||||
using AIStudio.Settings;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
using DialogOptions = AIStudio.Dialogs.DialogOptions;
|
||||
|
||||
namespace AIStudio.Components.Settings;
|
||||
|
||||
public partial class SettingsPanelEmbeddings : SettingsPanelBase
|
||||
{
|
||||
[Parameter]
|
||||
public List<ConfigurationSelectData<string>> AvailableEmbeddingProviders { get; set; } = new();
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<List<ConfigurationSelectData<string>>> AvailableEmbeddingProvidersChanged { get; set; }
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await this.UpdateEmbeddingProviders();
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private async Task AddEmbeddingProvider()
|
||||
{
|
||||
var dialogParameters = new DialogParameters<EmbeddingProviderDialog>
|
||||
{
|
||||
{ x => x.IsEditing, false },
|
||||
};
|
||||
|
||||
var dialogReference = await this.DialogService.ShowAsync<EmbeddingProviderDialog>("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);
|
||||
await 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<EmbeddingProviderDialog>
|
||||
{
|
||||
{ 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<EmbeddingProviderDialog>("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;
|
||||
await 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();
|
||||
}
|
||||
|
||||
await this.UpdateEmbeddingProviders();
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
|
||||
private async Task UpdateEmbeddingProviders()
|
||||
{
|
||||
this.AvailableEmbeddingProviders.Clear();
|
||||
foreach (var provider in this.SettingsManager.ConfigurationData.EmbeddingProviders)
|
||||
this.AvailableEmbeddingProviders.Add(new (provider.Name, provider.Id));
|
||||
|
||||
await this.AvailableEmbeddingProvidersChanged.InvokeAsync(this.AvailableEmbeddingProviders);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
@using AIStudio.Settings
|
||||
@inherits SettingsPanelBase
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Edit" HeaderText="Assistant: Grammar & Spelling Checker">
|
||||
<MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg">
|
||||
<ConfigurationOption OptionDescription="Preselect grammar & spell checker options?" LabelOn="Grammar & spell checker options are preselected" LabelOff="No grammar & spell checker options are preselected" State="@(() => this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectOptions = updatedState)" OptionHelp="When enabled, you can preselect the grammar & spell checker options. This is might be useful when you prefer a specific language or LLM model."/>
|
||||
<ConfigurationSelect OptionDescription="Preselect the target language" Disabled="@(() => !this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectedTargetLanguage)" Data="@ConfigurationSelectDataFactory.GetCommonLanguagesOptionalData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectedTargetLanguage = selectedValue)" OptionHelp="Which target language should be preselected?"/>
|
||||
@if (this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectedTargetLanguage is CommonLanguages.OTHER)
|
||||
{
|
||||
<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.AvailableLLMProvidersFunc()" Disabled="@(() => !this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectedProvider = selectedValue)"/>
|
||||
</MudPaper>
|
||||
</ExpansionPanel>
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace AIStudio.Components.Settings;
|
||||
|
||||
public partial class SettingsPanelGrammarSpelling : SettingsPanelBase;
|
||||
@@ -0,0 +1,11 @@
|
||||
@using AIStudio.Settings
|
||||
@inherits SettingsPanelBase
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.FindInPage" HeaderText="Assistant: Icon Finder Options">
|
||||
<MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg">
|
||||
<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.AvailableLLMProvidersFunc()" Disabled="@(() => !this.SettingsManager.ConfigurationData.IconFinder.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.IconFinder.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.IconFinder.PreselectedProvider = selectedValue)"/>
|
||||
</MudPaper>
|
||||
</ExpansionPanel>
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace AIStudio.Components.Settings;
|
||||
|
||||
public partial class SettingsPanelIconFinder : SettingsPanelBase;
|
||||
@@ -0,0 +1,22 @@
|
||||
@using AIStudio.Settings
|
||||
@inherits SettingsPanelBase
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Work" HeaderText="Assistant: Job Postings">
|
||||
<MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg">
|
||||
<ConfigurationOption OptionDescription="Preselect job posting options?" LabelOn="Job posting options are preselected" LabelOff="No job posting options are preselected" State="@(() => this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions = updatedState)" OptionHelp="When enabled, you can preselect some job posting options. This is might be useful when you prefer a specific LLM model."/>
|
||||
<ConfigurationText OptionDescription="Preselect the company name?" Disabled="@(() => !this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions)" Icon="@Icons.Material.Filled.Warehouse" Text="@(() => this.SettingsManager.ConfigurationData.JobPostings.PreselectedCompanyName)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.JobPostings.PreselectedCompanyName = updatedText)" />
|
||||
<ConfigurationText OptionDescription="Preselect some mandatory information about the job posting?" NumLines="6" Disabled="@(() => !this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions)" Icon="@Icons.Material.Filled.TextSnippet" Text="@(() => this.SettingsManager.ConfigurationData.JobPostings.PreselectedMandatoryInformation)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.JobPostings.PreselectedMandatoryInformation = updatedText)" />
|
||||
<ConfigurationText OptionDescription="Preselect the job description?" NumLines="3" MaxLines="6" Disabled="@(() => !this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions)" Icon="@Icons.Material.Filled.Settings" Text="@(() => this.SettingsManager.ConfigurationData.JobPostings.PreselectedJobDescription)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.JobPostings.PreselectedJobDescription = updatedText)" />
|
||||
<ConfigurationText OptionDescription="Preselect the job qualifications?" NumLines="3" MaxLines="6" Disabled="@(() => !this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions)" Icon="@Icons.Material.Filled.Settings" Text="@(() => this.SettingsManager.ConfigurationData.JobPostings.PreselectedQualifications)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.JobPostings.PreselectedQualifications = updatedText)" />
|
||||
<ConfigurationText OptionDescription="Preselect the job responsibilities?" NumLines="3" MaxLines="6" Disabled="@(() => !this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions)" Icon="@Icons.Material.Filled.Settings" Text="@(() => this.SettingsManager.ConfigurationData.JobPostings.PreselectedResponsibilities)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.JobPostings.PreselectedResponsibilities = updatedText)" />
|
||||
<ConfigurationText OptionDescription="Preselect the work location?" Disabled="@(() => !this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions)" Icon="@Icons.Material.Filled.MyLocation" Text="@(() => this.SettingsManager.ConfigurationData.JobPostings.PreselectedWorkLocation)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.JobPostings.PreselectedWorkLocation = updatedText)" />
|
||||
<ConfigurationText OptionDescription="Preselect the country (legal framework)?" Disabled="@(() => !this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions)" Icon="@Icons.Material.Filled.Flag" Text="@(() => this.SettingsManager.ConfigurationData.JobPostings.PreselectedCountryLegalFramework)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.JobPostings.PreselectedCountryLegalFramework = updatedText)" />
|
||||
<ConfigurationSelect OptionDescription="Preselect the language" Disabled="@(() => !this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.JobPostings.PreselectedTargetLanguage)" Data="@ConfigurationSelectDataFactory.GetCommonLanguagesOptionalData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.JobPostings.PreselectedTargetLanguage = selectedValue)" OptionHelp="Which target language should be preselected?"/>
|
||||
@if (this.SettingsManager.ConfigurationData.JobPostings.PreselectedTargetLanguage is CommonLanguages.OTHER)
|
||||
{
|
||||
<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.AvailableLLMProvidersFunc()" Disabled="@(() => !this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.JobPostings.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.JobPostings.PreselectedProvider = selectedValue)"/>
|
||||
</MudPaper>
|
||||
</ExpansionPanel>
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace AIStudio.Components.Settings;
|
||||
|
||||
public partial class SettingsPanelJobPostings : SettingsPanelBase;
|
||||
@@ -0,0 +1,14 @@
|
||||
@using AIStudio.Settings
|
||||
@inherits SettingsPanelBase
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Gavel" HeaderText="Assistant: Legal Check">
|
||||
<ConfigurationOption OptionDescription="Hide the web content reader?" LabelOn="Web content reader is hidden" LabelOff="Web content reader is shown" State="@(() => this.SettingsManager.ConfigurationData.LegalCheck.HideWebContentReader)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.LegalCheck.HideWebContentReader = updatedState)" OptionHelp="When activated, the web content reader is hidden and cannot be used. As a result, the user interface becomes a bit easier to use."/>
|
||||
<MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg">
|
||||
<ConfigurationOption OptionDescription="Preselect legal check options?" LabelOn="Legal check options are preselected" LabelOff="No legal check options are preselected" State="@(() => this.SettingsManager.ConfigurationData.LegalCheck.PreselectOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.LegalCheck.PreselectOptions = updatedState)" OptionHelp="When enabled, you can preselect some legal check options. This is might be useful when you prefer a specific LLM model."/>
|
||||
<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.AvailableLLMProvidersFunc()" 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>
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace AIStudio.Components.Settings;
|
||||
|
||||
public partial class SettingsPanelLegalCheck : SettingsPanelBase;
|
||||
@@ -0,0 +1,16 @@
|
||||
@using AIStudio.Settings
|
||||
@inherits SettingsPanelBase
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Task" HeaderText="Assistant: My Tasks">
|
||||
<MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg">
|
||||
<ConfigurationOption OptionDescription="Preselect options?" LabelOn="Options are preselected" LabelOff="No options are preselected" State="@(() => this.SettingsManager.ConfigurationData.MyTasks.PreselectOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.MyTasks.PreselectOptions = updatedState)" OptionHelp="When enabled, you can preselect options. This is might be useful when you prefer a specific language or LLM model."/>
|
||||
<ConfigurationSelect OptionDescription="Preselect the language" Disabled="@(() => !this.SettingsManager.ConfigurationData.MyTasks.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.MyTasks.PreselectedTargetLanguage)" Data="@ConfigurationSelectDataFactory.GetCommonLanguagesOptionalData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.MyTasks.PreselectedTargetLanguage = selectedValue)" OptionHelp="Which language should be preselected?"/>
|
||||
@if (this.SettingsManager.ConfigurationData.MyTasks.PreselectedTargetLanguage is CommonLanguages.OTHER)
|
||||
{
|
||||
<ConfigurationText OptionDescription="Preselect another language" Disabled="@(() => !this.SettingsManager.ConfigurationData.MyTasks.PreselectOptions)" Icon="@Icons.Material.Filled.Translate" Text="@(() => this.SettingsManager.ConfigurationData.MyTasks.PreselectOtherLanguage)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.MyTasks.PreselectOtherLanguage = updatedText)"/>
|
||||
}
|
||||
<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.AvailableLLMProvidersFunc()" Disabled="@(() => !this.SettingsManager.ConfigurationData.MyTasks.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.MyTasks.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.MyTasks.PreselectedProvider = selectedValue)"/>
|
||||
</MudPaper>
|
||||
</ExpansionPanel>
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace AIStudio.Components.Settings;
|
||||
|
||||
public partial class SettingsPanelMyTasks : SettingsPanelBase;
|
||||
@@ -0,0 +1,50 @@
|
||||
@inherits SettingsPanelBase
|
||||
|
||||
<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">
|
||||
Store personal data about yourself in various profiles so that the AIs know your personal context.
|
||||
This saves you from having to explain your context each time, for example, in every chat. When you
|
||||
have different roles, you can create a profile for each role.
|
||||
</MudJustifiedText>
|
||||
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
|
||||
Are you a project manager in a research facility? You might want to create a profile for your project
|
||||
management activities, one for your scientific work, and a profile for when you need to write program
|
||||
code. In these profiles, you can record how much experience you have or which methods you like or
|
||||
dislike using. Later, you can choose when and where you want to use each profile.
|
||||
</MudJustifiedText>
|
||||
<MudTable Items="@this.SettingsManager.ConfigurationData.Profiles" Hover="@true" Class="border-dashed border rounded-lg">
|
||||
<ColGroup>
|
||||
<col style="width: 3em;"/>
|
||||
<col/>
|
||||
<col style="width: 40em;"/>
|
||||
</ColGroup>
|
||||
<HeaderContent>
|
||||
<MudTh>#</MudTh>
|
||||
<MudTh>Profile Name</MudTh>
|
||||
<MudTh Style="text-align: left;">Actions</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd>@context.Num</MudTd>
|
||||
<MudTd>@context.Name</MudTd>
|
||||
<MudTd Style="text-align: left;">
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Edit" Class="ma-2" OnClick="() => this.EditProfile(context)">
|
||||
Edit
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Error" StartIcon="@Icons.Material.Filled.Delete" Class="ma-2" OnClick="() => this.DeleteProfile(context)">
|
||||
Delete
|
||||
</MudButton>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
|
||||
@if(this.SettingsManager.ConfigurationData.Profiles.Count == 0)
|
||||
{
|
||||
<MudText Typo="Typo.h6" Class="mt-3">No profiles configured yet.</MudText>
|
||||
}
|
||||
|
||||
<MudButton Variant="Variant.Filled" Color="@Color.Primary" StartIcon="@Icons.Material.Filled.AddRoad" Class="mt-3 mb-6" OnClick="@this.AddProfile">
|
||||
Add Profile
|
||||
</MudButton>
|
||||
</ExpansionPanel>
|
||||
@@ -0,0 +1,72 @@
|
||||
using AIStudio.Dialogs;
|
||||
using AIStudio.Settings;
|
||||
|
||||
using DialogOptions = AIStudio.Dialogs.DialogOptions;
|
||||
|
||||
namespace AIStudio.Components.Settings;
|
||||
|
||||
public partial class SettingsPanelProfiles : SettingsPanelBase
|
||||
{
|
||||
private async Task AddProfile()
|
||||
{
|
||||
var dialogParameters = new DialogParameters<ProfileDialog>
|
||||
{
|
||||
{ x => x.IsEditing, false },
|
||||
};
|
||||
|
||||
var dialogReference = await this.DialogService.ShowAsync<ProfileDialog>("Add Profile", dialogParameters, DialogOptions.FULLSCREEN);
|
||||
var dialogResult = await dialogReference.Result;
|
||||
if (dialogResult is null || dialogResult.Canceled)
|
||||
return;
|
||||
|
||||
var addedProfile = (Profile)dialogResult.Data!;
|
||||
addedProfile = addedProfile with { Num = this.SettingsManager.ConfigurationData.NextProfileNum++ };
|
||||
|
||||
this.SettingsManager.ConfigurationData.Profiles.Add(addedProfile);
|
||||
|
||||
await this.SettingsManager.StoreSettings();
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
|
||||
private async Task EditProfile(Profile profile)
|
||||
{
|
||||
var dialogParameters = new DialogParameters<ProfileDialog>
|
||||
{
|
||||
{ x => x.DataNum, profile.Num },
|
||||
{ x => x.DataId, profile.Id },
|
||||
{ x => x.DataName, profile.Name },
|
||||
{ x => x.DataNeedToKnow, profile.NeedToKnow },
|
||||
{ x => x.DataActions, profile.Actions },
|
||||
{ x => x.IsEditing, true },
|
||||
};
|
||||
|
||||
var dialogReference = await this.DialogService.ShowAsync<ProfileDialog>("Edit Profile", dialogParameters, DialogOptions.FULLSCREEN);
|
||||
var dialogResult = await dialogReference.Result;
|
||||
if (dialogResult is null || dialogResult.Canceled)
|
||||
return;
|
||||
|
||||
var editedProfile = (Profile)dialogResult.Data!;
|
||||
this.SettingsManager.ConfigurationData.Profiles[this.SettingsManager.ConfigurationData.Profiles.IndexOf(profile)] = editedProfile;
|
||||
|
||||
await this.SettingsManager.StoreSettings();
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
|
||||
private async Task DeleteProfile(Profile profile)
|
||||
{
|
||||
var dialogParameters = new DialogParameters
|
||||
{
|
||||
{ "Message", $"Are you sure you want to delete the profile '{profile.Name}'?" },
|
||||
};
|
||||
|
||||
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Delete Profile", dialogParameters, DialogOptions.FULLSCREEN);
|
||||
var dialogResult = await dialogReference.Result;
|
||||
if (dialogResult is null || dialogResult.Canceled)
|
||||
return;
|
||||
|
||||
this.SettingsManager.ConfigurationData.Profiles.Remove(profile);
|
||||
await this.SettingsManager.StoreSettings();
|
||||
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
@using AIStudio.Provider
|
||||
@using AIStudio.Settings
|
||||
@using AIStudio.Provider.SelfHosted
|
||||
@inherits SettingsPanelBase
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Layers" HeaderText="Configure Providers">
|
||||
<MudText Typo="Typo.h4" Class="mb-3">Configured Providers</MudText>
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
|
||||
What we call a provider is the combination of an LLM provider such as OpenAI and a model like GPT-4o.
|
||||
You can configure as many providers as you want. This way, you can use the appropriate model for each
|
||||
task. As an LLM provider, you can also choose local providers. However, to use this app, you must
|
||||
configure at least one provider.
|
||||
</MudJustifiedText>
|
||||
<MudTable Items="@this.SettingsManager.ConfigurationData.Providers" 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: 34em;"/>
|
||||
</ColGroup>
|
||||
<HeaderContent>
|
||||
<MudTh>#</MudTh>
|
||||
<MudTh>Instance Name</MudTh>
|
||||
<MudTh>Provider</MudTh>
|
||||
<MudTh>Model</MudTh>
|
||||
<MudTh Style="text-align: left;">Actions</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd>@context.Num</MudTd>
|
||||
<MudTd>@context.InstanceName</MudTd>
|
||||
<MudTd>@context.UsedLLMProvider</MudTd>
|
||||
<MudTd>
|
||||
@if (context.UsedLLMProvider is not LLMProviders.SELF_HOSTED)
|
||||
{
|
||||
@this.GetLLMProviderModelName(context)
|
||||
}
|
||||
else if (context.UsedLLMProvider is LLMProviders.SELF_HOSTED && context.Host is not Host.LLAMACPP)
|
||||
{
|
||||
@this.GetLLMProviderModelName(context)
|
||||
}
|
||||
else
|
||||
{
|
||||
@("as selected by provider")
|
||||
}
|
||||
</MudTd>
|
||||
<MudTd Style="text-align: left;">
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.OpenInBrowser" Class="ma-2" Href="@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.EditLLMProvider(context)">
|
||||
Edit
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Error" StartIcon="@Icons.Material.Filled.Delete" Class="ma-2" OnClick="() => this.DeleteLLMProvider(context)">
|
||||
Delete
|
||||
</MudButton>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
|
||||
@if(this.SettingsManager.ConfigurationData.Providers.Count == 0)
|
||||
{
|
||||
<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.AddLLMProvider">
|
||||
Add Provider
|
||||
</MudButton>
|
||||
|
||||
<MudText Typo="Typo.h4" Class="mb-3">LLM Provider Confidence</MudText>
|
||||
<MudJustifiedText Class="mb-3">
|
||||
Do you want to always be able to recognize how trustworthy your LLM providers are? This way,
|
||||
you keep control over which provider you send your data to. You have two options for this:
|
||||
Either you choose a common schema, or you configure the trust levels for each LLM provider yourself.
|
||||
</MudJustifiedText>
|
||||
|
||||
<ConfigurationOption OptionDescription="Do you want to enforce an app-wide minimum confidence level?" LabelOn="Yes, enforce a minimum confidence level" LabelOff="No, do not enforce a minimum confidence level" State="@(() => this.SettingsManager.ConfigurationData.LLMProviders.EnforceGlobalMinimumConfidence)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.LLMProviders.EnforceGlobalMinimumConfidence = updatedState)" OptionHelp="When enabled, you can enforce a minimum confidence level for all LLM providers. This way, you can ensure that only trustworthy providers are used."/>
|
||||
@if(this.SettingsManager.ConfigurationData.LLMProviders.EnforceGlobalMinimumConfidence)
|
||||
{
|
||||
<ConfigurationMinConfidenceSelection RestrictToGlobalMinimumConfidence="@false" SelectedValue="@(() => this.SettingsManager.ConfigurationData.LLMProviders.GlobalMinimumConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.LLMProviders.GlobalMinimumConfidence = selectedValue)"/>
|
||||
}
|
||||
|
||||
<ConfigurationOption OptionDescription="Show provider's confidence level?" LabelOn="Yes, show me the confidence level" LabelOff="No, please hide the confidence level" State="@(() => this.SettingsManager.ConfigurationData.LLMProviders.ShowProviderConfidence)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.LLMProviders.ShowProviderConfidence = updatedState)" OptionHelp="When enabled, we show you the confidence level for the selected provider in the app. This helps you assess where you are sending your data at any time. Example: are you currently working with sensitive data? Then choose a particularly trustworthy provider, etc."/>
|
||||
@if (this.SettingsManager.ConfigurationData.LLMProviders.ShowProviderConfidence)
|
||||
{
|
||||
<ConfigurationSelect OptionDescription="Select a confidence scheme" SelectedValue="@(() => this.SettingsManager.ConfigurationData.LLMProviders.ConfidenceScheme)" Data="@ConfigurationSelectDataFactory.GetConfidenceSchemesData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.LLMProviders.ConfidenceScheme = selectedValue)" OptionHelp="Choose the scheme that best suits you and your life. Do you trust any western provider? Or only providers from the USA or exclusively European providers? Then choose the appropriate scheme. Alternatively, you can assign the confidence levels to each provider yourself."/>
|
||||
@if (this.SettingsManager.ConfigurationData.LLMProviders.ConfidenceScheme is ConfidenceSchemes.CUSTOM)
|
||||
{
|
||||
<MudTable Items="@(Enum.GetValues<LLMProviders>().Where(x => x is not LLMProviders.NONE))" Hover="@true" Class="border-dashed border rounded-lg">
|
||||
<ColGroup>
|
||||
<col style="width: 12em;"/>
|
||||
<col/>
|
||||
<col style="width: 22em;"/>
|
||||
</ColGroup>
|
||||
<HeaderContent>
|
||||
<MudTh>LLM Provider</MudTh>
|
||||
<MudTh>Description</MudTh>
|
||||
<MudTh>Confidence Level</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd Style="vertical-align: top;">
|
||||
@context.ToName()
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<MudMarkdown Value="@context.GetConfidence(this.SettingsManager).Description"/>
|
||||
</MudTd>
|
||||
<MudTd Style="vertical-align: top;">
|
||||
<MudMenu StartIcon="@Icons.Material.Filled.Security" EndIcon="@Icons.Material.Filled.KeyboardArrowDown" Label="@this.GetCurrentConfidenceLevelName(context)" Variant="Variant.Filled" Style="@this.SetCurrentConfidenceLevelColorStyle(context)">
|
||||
@foreach (var confidenceLevel in Enum.GetValues<ConfidenceLevel>().OrderBy(n => n))
|
||||
{
|
||||
if(confidenceLevel is ConfidenceLevel.NONE or ConfidenceLevel.UNKNOWN)
|
||||
continue;
|
||||
|
||||
<MudMenuItem OnClick="@(async () => await this.ChangeCustomConfidenceLevel(context, confidenceLevel))">
|
||||
@confidenceLevel.GetName()
|
||||
</MudMenuItem>
|
||||
}
|
||||
</MudMenu>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
}
|
||||
}
|
||||
</ExpansionPanel>
|
||||
@@ -0,0 +1,151 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
using AIStudio.Dialogs;
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Settings;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
using DialogOptions = AIStudio.Dialogs.DialogOptions;
|
||||
|
||||
namespace AIStudio.Components.Settings;
|
||||
|
||||
public partial class SettingsPanelProviders : SettingsPanelBase
|
||||
{
|
||||
[Parameter]
|
||||
public List<ConfigurationSelectData<string>> AvailableLLMProviders { get; set; } = new();
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<List<ConfigurationSelectData<string>>> AvailableLLMProvidersChanged { get; set; }
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await this.UpdateProviders();
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
[SuppressMessage("Usage", "MWAIS0001:Direct access to `Providers` is not allowed")]
|
||||
private async Task AddLLMProvider()
|
||||
{
|
||||
var dialogParameters = new DialogParameters<ProviderDialog>
|
||||
{
|
||||
{ x => x.IsEditing, false },
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
var addedProvider = (AIStudio.Settings.Provider)dialogResult.Data!;
|
||||
addedProvider = addedProvider with { Num = this.SettingsManager.ConfigurationData.NextProviderNum++ };
|
||||
|
||||
this.SettingsManager.ConfigurationData.Providers.Add(addedProvider);
|
||||
await this.UpdateProviders();
|
||||
|
||||
await this.SettingsManager.StoreSettings();
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
|
||||
[SuppressMessage("Usage", "MWAIS0001:Direct access to `Providers` is not allowed")]
|
||||
private async Task EditLLMProvider(AIStudio.Settings.Provider provider)
|
||||
{
|
||||
var dialogParameters = new DialogParameters<ProviderDialog>
|
||||
{
|
||||
{ x => x.DataNum, provider.Num },
|
||||
{ x => x.DataId, provider.Id },
|
||||
{ x => x.DataInstanceName, provider.InstanceName },
|
||||
{ x => x.DataLLMProvider, provider.UsedLLMProvider },
|
||||
{ x => x.DataModel, provider.Model },
|
||||
{ x => x.DataHostname, provider.Hostname },
|
||||
{ x => x.IsSelfHosted, provider.IsSelfHosted },
|
||||
{ x => x.IsEditing, true },
|
||||
{ x => x.DataHost, provider.Host },
|
||||
};
|
||||
|
||||
var dialogReference = await this.DialogService.ShowAsync<ProviderDialog>("Edit LLM Provider", dialogParameters, DialogOptions.FULLSCREEN);
|
||||
var dialogResult = await dialogReference.Result;
|
||||
if (dialogResult is null || dialogResult.Canceled)
|
||||
return;
|
||||
|
||||
var editedProvider = (AIStudio.Settings.Provider)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(editedProvider.Num == 0)
|
||||
editedProvider = editedProvider with { Num = this.SettingsManager.ConfigurationData.NextProviderNum++ };
|
||||
|
||||
this.SettingsManager.ConfigurationData.Providers[this.SettingsManager.ConfigurationData.Providers.IndexOf(provider)] = editedProvider;
|
||||
await this.UpdateProviders();
|
||||
|
||||
await this.SettingsManager.StoreSettings();
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
|
||||
[SuppressMessage("Usage", "MWAIS0001:Direct access to `Providers` is not allowed")]
|
||||
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 LLM 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.Providers.Remove(provider);
|
||||
await this.SettingsManager.StoreSettings();
|
||||
}
|
||||
|
||||
await this.UpdateProviders();
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
|
||||
private string GetLLMProviderModelName(AIStudio.Settings.Provider provider)
|
||||
{
|
||||
const int MAX_LENGTH = 36;
|
||||
var modelName = provider.Model.ToString();
|
||||
return modelName.Length > MAX_LENGTH ? "[...] " + modelName[^Math.Min(MAX_LENGTH, modelName.Length)..] : modelName;
|
||||
}
|
||||
|
||||
[SuppressMessage("Usage", "MWAIS0001:Direct access to `Providers` is not allowed")]
|
||||
private async Task UpdateProviders()
|
||||
{
|
||||
this.AvailableLLMProviders.Clear();
|
||||
foreach (var provider in this.SettingsManager.ConfigurationData.Providers)
|
||||
this.AvailableLLMProviders.Add(new (provider.InstanceName, provider.Id));
|
||||
|
||||
await this.AvailableLLMProvidersChanged.InvokeAsync(this.AvailableLLMProviders);
|
||||
}
|
||||
|
||||
private string GetCurrentConfidenceLevelName(LLMProviders llmProvider)
|
||||
{
|
||||
if (this.SettingsManager.ConfigurationData.LLMProviders.CustomConfidenceScheme.TryGetValue(llmProvider, out var level))
|
||||
return level.GetName();
|
||||
|
||||
return "Not yet configured";
|
||||
}
|
||||
|
||||
private string SetCurrentConfidenceLevelColorStyle(LLMProviders llmProvider)
|
||||
{
|
||||
if (this.SettingsManager.ConfigurationData.LLMProviders.CustomConfidenceScheme.TryGetValue(llmProvider, out var level))
|
||||
return $"background-color: {level.GetColor(this.SettingsManager)};";
|
||||
|
||||
return $"background-color: {ConfidenceLevel.UNKNOWN.GetColor(this.SettingsManager)};";
|
||||
}
|
||||
|
||||
private async Task ChangeCustomConfidenceLevel(LLMProviders llmProvider, ConfidenceLevel level)
|
||||
{
|
||||
this.SettingsManager.ConfigurationData.LLMProviders.CustomConfidenceScheme[llmProvider] = level;
|
||||
await this.SettingsManager.StoreSettings();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
@using AIStudio.Settings
|
||||
@inherits SettingsPanelBase
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Edit" HeaderText="Assistant: Rewrite & Improve Text">
|
||||
<MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg">
|
||||
<ConfigurationOption OptionDescription="Preselect rewrite & improve text options?" LabelOn="Rewrite & improve text options are preselected" LabelOff="No rewrite & improve text options are preselected" State="@(() => this.SettingsManager.ConfigurationData.RewriteImprove.PreselectOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.RewriteImprove.PreselectOptions = updatedState)" OptionHelp="When enabled, you can preselect the rewrite & improve text options. This is might be useful when you prefer a specific language or LLM model."/>
|
||||
<ConfigurationSelect OptionDescription="Preselect the target language" Disabled="@(() => !this.SettingsManager.ConfigurationData.RewriteImprove.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedTargetLanguage)" Data="@ConfigurationSelectDataFactory.GetCommonLanguagesOptionalData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedTargetLanguage = selectedValue)" OptionHelp="Which target language should be preselected?"/>
|
||||
@if (this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedTargetLanguage is CommonLanguages.OTHER)
|
||||
{
|
||||
<ConfigurationText OptionDescription="Preselect another target language" Disabled="@(() => !this.SettingsManager.ConfigurationData.RewriteImprove.PreselectOptions)" Icon="@Icons.Material.Filled.Translate" Text="@(() => this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedOtherLanguage)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedOtherLanguage = updatedText)"/>
|
||||
}
|
||||
<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.AvailableLLMProvidersFunc()" Disabled="@(() => !this.SettingsManager.ConfigurationData.RewriteImprove.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedProvider = selectedValue)"/>
|
||||
</MudPaper>
|
||||
</ExpansionPanel>
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace AIStudio.Components.Settings;
|
||||
|
||||
public partial class SettingsPanelRewrite : SettingsPanelBase;
|
||||
@@ -0,0 +1,15 @@
|
||||
@using AIStudio.Settings
|
||||
@inherits SettingsPanelBase
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Spellcheck" HeaderText="Assistant: Synonyms">
|
||||
<MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg">
|
||||
<ConfigurationOption OptionDescription="Preselect synonym options?" LabelOn="Synonym options are preselected" LabelOff="No synonym options are preselected" State="@(() => this.SettingsManager.ConfigurationData.Synonyms.PreselectOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.Synonyms.PreselectOptions = updatedState)" OptionHelp="When enabled, you can preselect synonym options. This is might be useful when you prefer a specific language or LLM model."/>
|
||||
<ConfigurationSelect OptionDescription="Preselect the language" Disabled="@(() => !this.SettingsManager.ConfigurationData.Synonyms.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Synonyms.PreselectedLanguage)" Data="@ConfigurationSelectDataFactory.GetCommonLanguagesOptionalData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Synonyms.PreselectedLanguage = selectedValue)" OptionHelp="Which language should be preselected?"/>
|
||||
@if (this.SettingsManager.ConfigurationData.Synonyms.PreselectedLanguage is CommonLanguages.OTHER)
|
||||
{
|
||||
<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.AvailableLLMProvidersFunc()" Disabled="@(() => !this.SettingsManager.ConfigurationData.Synonyms.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Synonyms.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Synonyms.PreselectedProvider = selectedValue)"/>
|
||||
</MudPaper>
|
||||
</ExpansionPanel>
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace AIStudio.Components.Settings;
|
||||
|
||||
public partial class SettingsPanelSynonyms : SettingsPanelBase;
|
||||
@@ -0,0 +1,24 @@
|
||||
@using AIStudio.Assistants.TextSummarizer
|
||||
@using AIStudio.Settings
|
||||
@inherits SettingsPanelBase
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.TextSnippet" HeaderText="Assistant: Text Summarizer Options">
|
||||
<ConfigurationOption OptionDescription="Hide the web content reader?" LabelOn="Web content reader is hidden" LabelOff="Web content reader is shown" State="@(() => this.SettingsManager.ConfigurationData.TextSummarizer.HideWebContentReader)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.TextSummarizer.HideWebContentReader = updatedState)" OptionHelp="When activated, the web content reader is hidden and cannot be used. As a result, the user interface becomes a bit easier to use."/>
|
||||
<MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg">
|
||||
<ConfigurationOption OptionDescription="Preselect summarizer options?" LabelOn="Summarizer options are preselected" LabelOff="No summarizer options are preselected" State="@(() => this.SettingsManager.ConfigurationData.TextSummarizer.PreselectOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.TextSummarizer.PreselectOptions = updatedState)" OptionHelp="When enabled, you can preselect the text summarizer options. This is might be useful when you prefer a specific language, complexity, or LLM."/>
|
||||
<ConfigurationOption OptionDescription="Preselect the web content reader?" Disabled="@(() => !this.SettingsManager.ConfigurationData.TextSummarizer.PreselectOptions || this.SettingsManager.ConfigurationData.TextSummarizer.HideWebContentReader)" LabelOn="Web content reader is preselected" LabelOff="Web content reader is not preselected" State="@(() => this.SettingsManager.ConfigurationData.TextSummarizer.PreselectWebContentReader)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.TextSummarizer.PreselectWebContentReader = updatedState)" OptionHelp="When enabled, the web content reader is preselected. This is might be useful when you prefer to load content from the web very often."/>
|
||||
<ConfigurationOption OptionDescription="Preselect the content cleaner agent?" Disabled="@(() => !this.SettingsManager.ConfigurationData.TextSummarizer.PreselectOptions || this.SettingsManager.ConfigurationData.TextSummarizer.HideWebContentReader)" LabelOn="Content cleaner agent is preselected" LabelOff="Content cleaner agent is not preselected" State="@(() => this.SettingsManager.ConfigurationData.TextSummarizer.PreselectContentCleanerAgent)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.TextSummarizer.PreselectContentCleanerAgent = updatedState)" OptionHelp="When enabled, the content cleaner agent is preselected. This is might be useful when you prefer to clean up the content before summarize it."/>
|
||||
<ConfigurationSelect OptionDescription="Preselect the target language" Disabled="@(() => !this.SettingsManager.ConfigurationData.TextSummarizer.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedTargetLanguage)" Data="@ConfigurationSelectDataFactory.GetCommonLanguagesData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedTargetLanguage = selectedValue)" OptionHelp="Which target language should be preselected?"/>
|
||||
@if (this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedTargetLanguage is CommonLanguages.OTHER)
|
||||
{
|
||||
<ConfigurationText OptionDescription="Preselect another target language" Disabled="@(() => !this.SettingsManager.ConfigurationData.TextSummarizer.PreselectOptions)" Icon="@Icons.Material.Filled.Translate" Text="@(() => this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedOtherLanguage)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedOtherLanguage = updatedText)"/>
|
||||
}
|
||||
<ConfigurationSelect OptionDescription="Preselect the summarizer complexity" Disabled="@(() => !this.SettingsManager.ConfigurationData.TextSummarizer.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedComplexity)" Data="@ConfigurationSelectDataFactory.GetComplexityData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedComplexity = selectedValue)" OptionHelp="Which summarizer complexity should be preselected?"/>
|
||||
@if(this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedComplexity is Complexity.SCIENTIFIC_LANGUAGE_OTHER_EXPERTS)
|
||||
{
|
||||
<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.AvailableLLMProvidersFunc()" Disabled="@(() => !this.SettingsManager.ConfigurationData.TextSummarizer.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedProvider = selectedValue)"/>
|
||||
</MudPaper>
|
||||
</ExpansionPanel>
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace AIStudio.Components.Settings;
|
||||
|
||||
public partial class SettingsPanelTextSummarizer : SettingsPanelBase;
|
||||
@@ -0,0 +1,20 @@
|
||||
@using AIStudio.Settings
|
||||
@inherits SettingsPanelBase
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Translate" HeaderText="Assistant: Translator Options">
|
||||
<ConfigurationSlider T="int" OptionDescription="How fast should the live translation react?" Min="500" Max="3_000" Step="100" Unit="milliseconds" Value="@(() => this.SettingsManager.ConfigurationData.Translation.DebounceIntervalMilliseconds)" ValueUpdate="@(updatedValue => this.SettingsManager.ConfigurationData.Translation.DebounceIntervalMilliseconds = updatedValue)"/>
|
||||
<ConfigurationOption OptionDescription="Hide the web content reader?" LabelOn="Web content reader is hidden" LabelOff="Web content reader is shown" State="@(() => this.SettingsManager.ConfigurationData.Translation.HideWebContentReader)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.Translation.HideWebContentReader = updatedState)" OptionHelp="When activated, the web content reader is hidden and cannot be used. As a result, the user interface becomes a bit easier to use."/>
|
||||
<MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg">
|
||||
<ConfigurationOption OptionDescription="Preselect translator options?" LabelOn="Translator options are preselected" LabelOff="No translator options are preselected" State="@(() => this.SettingsManager.ConfigurationData.Translation.PreselectOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.Translation.PreselectOptions = updatedState)" OptionHelp="When enabled, you can preselect the translator options. This is might be useful when you prefer a specific target language or LLM model."/>
|
||||
<ConfigurationOption OptionDescription="Preselect the web content reader?" Disabled="@(() => !this.SettingsManager.ConfigurationData.Translation.PreselectOptions || this.SettingsManager.ConfigurationData.Translation.HideWebContentReader)" LabelOn="Web content reader is preselected" LabelOff="Web content reader is not preselected" State="@(() => this.SettingsManager.ConfigurationData.Translation.PreselectWebContentReader)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.Translation.PreselectWebContentReader = updatedState)" OptionHelp="When enabled, the web content reader is preselected. This is might be useful when you prefer to load content from the web very often."/>
|
||||
<ConfigurationOption OptionDescription="Preselect the content cleaner agent?" Disabled="@(() => !this.SettingsManager.ConfigurationData.Translation.PreselectOptions || this.SettingsManager.ConfigurationData.Translation.HideWebContentReader)" LabelOn="Content cleaner agent is preselected" LabelOff="Content cleaner agent is not preselected" State="@(() => this.SettingsManager.ConfigurationData.Translation.PreselectContentCleanerAgent)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.Translation.PreselectContentCleanerAgent = updatedState)" OptionHelp="When enabled, the content cleaner agent is preselected. This is might be useful when you prefer to clean up the content before translating it."/>
|
||||
<ConfigurationOption OptionDescription="Preselect live translation?" Disabled="@(() => !this.SettingsManager.ConfigurationData.Translation.PreselectOptions)" LabelOn="Live translation is preselected" LabelOff="Live translation is not preselected" State="@(() => this.SettingsManager.ConfigurationData.Translation.PreselectLiveTranslation)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.Translation.PreselectLiveTranslation = updatedState)" />
|
||||
<ConfigurationSelect OptionDescription="Preselect the target language" Disabled="@(() => !this.SettingsManager.ConfigurationData.Translation.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Translation.PreselectedTargetLanguage)" Data="@ConfigurationSelectDataFactory.GetCommonLanguagesData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Translation.PreselectedTargetLanguage = selectedValue)" OptionHelp="Which target language should be preselected?"/>
|
||||
@if (this.SettingsManager.ConfigurationData.Translation.PreselectedTargetLanguage is CommonLanguages.OTHER)
|
||||
{
|
||||
<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.AvailableLLMProvidersFunc()" Disabled="@(() => !this.SettingsManager.ConfigurationData.Translation.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Translation.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Translation.PreselectedProvider = selectedValue)"/>
|
||||
</MudPaper>
|
||||
</ExpansionPanel>
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace AIStudio.Components.Settings;
|
||||
|
||||
public partial class SettingsPanelTranslation : SettingsPanelBase;
|
||||
@@ -0,0 +1,13 @@
|
||||
@using AIStudio.Settings
|
||||
@using AIStudio.Settings.DataModel
|
||||
@inherits SettingsPanelBase
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Work" HeaderText="Workspace Options">
|
||||
<ConfigurationSelect OptionDescription="Workspace behavior" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Workspace.StorageBehavior)" Data="@ConfigurationSelectDataFactory.GetWorkspaceStorageBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Workspace.StorageBehavior = selectedValue)" OptionHelp="Should we store your chats?"/>
|
||||
|
||||
@if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is not WorkspaceStorageBehavior.DISABLE_WORKSPACES)
|
||||
{
|
||||
<ConfigurationSelect OptionDescription="Workspace maintenance" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Workspace.StorageTemporaryMaintenancePolicy)" Data="@ConfigurationSelectDataFactory.GetWorkspaceStorageTemporaryMaintenancePolicyData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Workspace.StorageTemporaryMaintenancePolicy = selectedValue)" OptionHelp="If and when should we delete your temporary chats?"/>
|
||||
<ConfigurationSelect OptionDescription="Workspace display behavior" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Workspace.DisplayBehavior)" Data="@ConfigurationSelectDataFactory.GetWorkspaceDisplayBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Workspace.DisplayBehavior = selectedValue)" OptionHelp="How should we display your workspaces?"/>
|
||||
}
|
||||
</ExpansionPanel>
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace AIStudio.Components.Settings;
|
||||
|
||||
public partial class SettingsPanelWorkspaces : SettingsPanelBase;
|
||||
@@ -0,0 +1,19 @@
|
||||
@using AIStudio.Settings
|
||||
@inherits SettingsPanelBase
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Email" HeaderText="Assistant: Writing E-Mails">
|
||||
<MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg">
|
||||
<ConfigurationOption OptionDescription="Preselect e-mail options?" LabelOn="E-Mail options are preselected" LabelOff="No e-mail options are preselected" State="@(() => this.SettingsManager.ConfigurationData.EMail.PreselectOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.EMail.PreselectOptions = updatedState)" OptionHelp="When enabled, you can preselect the e-mail options. This is might be useful when you prefer a specific language or LLM model."/>
|
||||
<ConfigurationText OptionDescription="Preselect a greeting?" Disabled="@(() => !this.SettingsManager.ConfigurationData.EMail.PreselectOptions)" Icon="@Icons.Material.Filled.Person" Text="@(() => this.SettingsManager.ConfigurationData.EMail.Greeting)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.EMail.Greeting = updatedText)" />
|
||||
<ConfigurationText OptionDescription="Preselect your name for the closing salutation?" Disabled="@(() => !this.SettingsManager.ConfigurationData.EMail.PreselectOptions)" Icon="@Icons.Material.Filled.Person" Text="@(() => this.SettingsManager.ConfigurationData.EMail.SenderName)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.EMail.SenderName = updatedText)" />
|
||||
<ConfigurationSelect OptionDescription="Preselect the target language" Disabled="@(() => !this.SettingsManager.ConfigurationData.EMail.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.EMail.PreselectedTargetLanguage)" Data="@ConfigurationSelectDataFactory.GetCommonLanguagesTranslationData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.EMail.PreselectedTargetLanguage = selectedValue)" OptionHelp="Which target language should be preselected?"/>
|
||||
@if (this.SettingsManager.ConfigurationData.EMail.PreselectedTargetLanguage is CommonLanguages.OTHER)
|
||||
{
|
||||
<ConfigurationText OptionDescription="Preselect another target language" Disabled="@(() => !this.SettingsManager.ConfigurationData.EMail.PreselectOptions)" Icon="@Icons.Material.Filled.Translate" Text="@(() => this.SettingsManager.ConfigurationData.EMail.PreselectOtherLanguage)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.EMail.PreselectOtherLanguage = updatedText)"/>
|
||||
}
|
||||
<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.AvailableLLMProvidersFunc()" 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>
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace AIStudio.Components.Settings;
|
||||
|
||||
public partial class SettingsPanelWritingEMails : SettingsPanelBase;
|
||||
Loaded 100 of 258 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user