Refactor components (#80)

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2024-08-21 08:30:01 +02:00
1 parent a18cb22263
commit 1d1af91f3e
137 files changed
+220 -220

No files matched your search

@@ -1,28 +0,0 @@
@* ReSharper disable Html.PathError *@
<!DOCTYPE html>
<html lang="en">
<head>
<title>MindWork AI Studio</title>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<base href="/"/>
<link rel="icon" type="image/png" href="favicon.png"/>
<link href="system/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
<link href="system/MudBlazor.Markdown/MudBlazor.Markdown.min.css" rel="stylesheet" />
<link href="app.css" rel="stylesheet" />
<HeadOutlet/>
<script src="diff.js"></script>
</head>
<body style="overflow: hidden;">
<Routes @rendermode="new InteractiveServerRenderMode(prerender: false)"/>
<script src="_framework/blazor.web.js" autostart="false"></script>
<script src="boot.js"></script>
<script src="system/MudBlazor/MudBlazor.min.js"></script>
<script src="system/MudBlazor.Markdown/MudBlazor.Markdown.min.js"></script>
<script src="app.js"></script>
</body>
</html>
@@ -1,78 +0,0 @@
@using AIStudio.Chat
@using AIStudio.Components.Pages
@using AIStudio.Tools
<MudText Typo="Typo.h3" Class="mb-2 mr-3">
@(this.Title)
</MudText>
<InnerScrolling HeaderHeight="12.3em">
<ChildContent>
<MudForm @ref="@(this.form)" @bind-IsValid="@(this.inputIsValid)" @bind-Errors="@(this.inputIssues)" Class="pr-2">
<MudText Typo="Typo.body1" Align="Align.Justify" Class="mb-6">
@(this.Description)
</MudText>
@if (this.Body is not null)
{
@(this.Body)
}
</MudForm>
<Issues IssuesData="@(this.inputIssues)"/>
@if (this.ShowDedicatedProgress && this.isProcessing)
{
<MudProgressLinear Color="Color.Primary" Indeterminate="true" Class="mb-6" />
}
<div id="@ASSISTANT_RESULT_DIV_ID" class="mr-2 mt-3">
@if (this.ShowResult && this.resultingContentBlock is not null)
{
<ContentBlockComponent Role="@(this.resultingContentBlock.Role)" Type="@(this.resultingContentBlock.ContentType)" Time="@(this.resultingContentBlock.Time)" Content="@(this.resultingContentBlock.Content)"/>
}
</div>
<div id="@AFTER_RESULT_DIV_ID" class="mt-3">
</div>
@if (this.FooterButtons.Count > 0)
{
<MudStack Row="@true" Wrap="Wrap.Wrap" Class="mt-3 mr-2">
@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;
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:
<MudMenu StartIcon="@Icons.Material.Filled.Apps" EndIcon="@Icons.Material.Filled.KeyboardArrowDown" Label="Send to ..." Variant="Variant.Filled" Color="Color.Info">
@foreach (var assistant in Enum.GetValues<SendTo>().OrderBy(n => n.Name().Length))
{
if(assistant is SendTo.NONE || sendToButton.Self == assistant)
continue;
<MudMenuItem OnClick="() => this.SendToAssistant(assistant, sendToButton)">
@assistant.Name()
</MudMenuItem>
}
</MudMenu>
break;
}
}
<MudButton Variant="Variant.Filled" Color="Color.Warning" StartIcon="@Icons.Material.Filled.Refresh" OnClick="() => this.InnerResetForm()">
Reset
</MudButton>
</MudStack>
}
</ChildContent>
</InnerScrolling>
@@ -1,232 +0,0 @@
using AIStudio.Chat;
using AIStudio.Components.Pages;
using AIStudio.Provider;
using AIStudio.Settings;
using AIStudio.Tools;
using Microsoft.AspNetCore.Components;
using Path = AIStudio.Components.Pages.Path;
namespace AIStudio.Components;
public abstract partial class AssistantBase : ComponentBase
{
[Inject]
protected SettingsManager SettingsManager { get; set; } = null!;
[Inject]
protected IJSRuntime JsRuntime { get; init; } = null!;
[Inject]
protected ThreadSafeRandom RNG { get; init; } = null!;
[Inject]
protected ISnackbar Snackbar { get; init; } = null!;
[Inject]
protected Rust Rust { get; init; } = null!;
[Inject]
protected NavigationManager NavigationManager { get; init; } = null!;
internal const string AFTER_RESULT_DIV_ID = "afterAssistantResult";
internal const string ASSISTANT_RESULT_DIV_ID = "assistantResult";
protected abstract string Title { get; }
protected abstract string Description { get; }
protected abstract string SystemPrompt { get; }
protected abstract void ResetFrom();
protected abstract bool MightPreselectValues();
private protected virtual RenderFragment? Body => null;
protected virtual bool ShowResult => true;
protected virtual bool ShowDedicatedProgress => false;
protected virtual ChatThread ConvertToChatThread => this.chatThread ?? new();
protected virtual IReadOnlyList<IButtonData> FooterButtons => [];
protected static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new();
protected AIStudio.Settings.Provider providerSettings;
protected MudForm? form;
protected bool inputIsValid;
protected ChatThread? chatThread;
private ContentBlock? resultingContentBlock;
private string[] inputIssues = [];
private bool isProcessing;
#region Overrides of ComponentBase
protected override async Task OnParametersSetAsync()
{
// Configure the spellchecking for the user input:
this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES);
await base.OnParametersSetAsync();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
// Reset the validation when not editing and on the first render.
// We don't want to show validation errors when the user opens the dialog.
if(firstRender)
this.form?.ResetValidation();
await base.OnAfterRenderAsync(firstRender);
}
#endregion
protected string? ValidatingProvider(AIStudio.Settings.Provider provider)
{
if(provider.UsedProvider == Providers.NONE)
return "Please select a provider.";
return null;
}
protected void CreateChatThread()
{
this.chatThread = new()
{
WorkspaceId = Guid.Empty,
ChatId = Guid.NewGuid(),
Name = string.Empty,
Seed = this.RNG.Next(),
SystemPrompt = this.SystemPrompt,
Blocks = [],
};
}
protected DateTimeOffset AddUserRequest(string request)
{
var time = DateTimeOffset.Now;
this.chatThread!.Blocks.Add(new ContentBlock
{
Time = time,
ContentType = ContentType.TEXT,
Role = ChatRole.USER,
Content = new ContentText
{
Text = request,
},
});
return time;
}
protected async Task<string> AddAIResponseAsync(DateTimeOffset time)
{
var aiText = new ContentText
{
// We have to wait for the remote
// for the content stream:
InitialRemoteWait = true,
};
this.resultingContentBlock = new ContentBlock
{
Time = time,
ContentType = ContentType.TEXT,
Role = ChatRole.AI,
Content = aiText,
};
this.chatThread?.Blocks.Add(this.resultingContentBlock);
this.isProcessing = true;
this.StateHasChanged();
// 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.JsRuntime, this.SettingsManager, this.providerSettings.Model, this.chatThread);
this.isProcessing = false;
this.StateHasChanged();
// Return the AI response:
return aiText.Text;
}
protected async Task CopyToClipboard(string text)
{
await this.Rust.CopyText2Clipboard(this.JsRuntime, this.Snackbar, text);
}
private static string? GetButtonIcon(string icon)
{
if(string.IsNullOrWhiteSpace(icon))
return null;
return icon;
}
private Task SendToAssistant(SendTo destination, SendToButton sendToButton)
{
var contentToSend = sendToButton.UseResultingContentBlockData switch
{
false => sendToButton.GetText(),
true => this.resultingContentBlock?.Content switch
{
ContentText textBlock => textBlock.Text,
_ => string.Empty,
},
};
var (eventItem, path) = destination switch
{
SendTo.AGENDA_ASSISTANT => (Event.SEND_TO_AGENDA_ASSISTANT, Path.ASSISTANT_AGENDA),
SendTo.CODING_ASSISTANT => (Event.SEND_TO_CODING_ASSISTANT, Path.ASSISTANT_CODING),
SendTo.REWRITE_ASSISTANT => (Event.SEND_TO_REWRITE_ASSISTANT, Path.ASSISTANT_REWRITE),
SendTo.TRANSLATION_ASSISTANT => (Event.SEND_TO_TRANSLATION_ASSISTANT, Path.ASSISTANT_TRANSLATION),
SendTo.ICON_FINDER_ASSISTANT => (Event.SEND_TO_ICON_FINDER_ASSISTANT, Path.ASSISTANT_ICON_FINDER),
SendTo.GRAMMAR_SPELLING_ASSISTANT => (Event.SEND_TO_GRAMMAR_SPELLING_ASSISTANT, Path.ASSISTANT_GRAMMAR_SPELLING),
SendTo.TEXT_SUMMARIZER_ASSISTANT => (Event.SEND_TO_TEXT_SUMMARIZER_ASSISTANT, Path.ASSISTANT_SUMMARIZER),
SendTo.CHAT => (Event.SEND_TO_CHAT, Path.CHAT),
_ => (Event.NONE, Path.ASSISTANTS),
};
switch (destination)
{
case SendTo.CHAT:
MessageBus.INSTANCE.DeferMessage(this, eventItem, this.ConvertToChatThread);
break;
default:
MessageBus.INSTANCE.DeferMessage(this, eventItem, contentToSend);
break;
}
this.NavigationManager.NavigateTo(path);
return Task.CompletedTask;
}
private async Task InnerResetForm()
{
this.resultingContentBlock = null;
this.providerSettings = default;
await this.JsRuntime.ClearDiv(ASSISTANT_RESULT_DIV_ID);
await this.JsRuntime.ClearDiv(AFTER_RESULT_DIV_ID);
this.ResetFrom();
this.inputIsValid = false;
this.inputIssues = [];
this.form?.ResetValidation();
this.StateHasChanged();
this.form?.ResetValidation();
}
}
@@ -1,19 +0,0 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
namespace AIStudio.Components;
//
// See https://stackoverflow.com/a/77300384/2258393 for why this class is necessary
//
public abstract class AssistantBaseCore : AssistantBase
{
private protected sealed override RenderFragment Body => this.BuildRenderTree;
// Allow content to be provided by a .razor file but without
// overriding the content of the base class
protected new virtual void BuildRenderTree(RenderTreeBuilder builder)
{
}
}
@@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components.Blocks;
namespace AIStudio.Components;
public partial class AssistantBlock : ComponentBase
{
@@ -1,3 +0,0 @@
namespace AIStudio.Components.Blocks;
public interface ITreeItem;
@@ -1,3 +0,0 @@
namespace AIStudio.Components.Blocks;
public readonly record struct TreeDivider : ITreeItem;
@@ -1,4 +1,4 @@
namespace AIStudio.Components.Blocks;
namespace AIStudio.Components;
public partial class Changelog
{
@@ -1,4 +1,3 @@
@using AIStudio.Tools
<MudSelect T="Log" @bind-Value="@this.SelectedLog" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Schedule" Margin="Margin.Dense" Label="Changelog" Class="mb-2 rounded-lg" Variant="Variant.Outlined" SelectedValuesChanged="() => this.ReadLogAsync()" OnKeyUp="() => this.ReadLogAsync()">
@foreach (var log in LOGS)
{
@@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components.Blocks;
namespace AIStudio.Components;
public partial class Changelog : ComponentBase
{
@@ -1,9 +0,0 @@
<MudDialog>
<DialogContent>
<MudText Typo="Typo.body1">@this.Message</MudText>
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Cancel" Variant="Variant.Filled">No</MudButton>
<MudButton OnClick="@this.Confirm" Variant="Variant.Filled" Color="Color.Error">Yes</MudButton>
</DialogActions>
</MudDialog>
@@ -1,19 +0,0 @@
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components.CommonDialogs;
/// <summary>
/// A confirmation dialog that can be used to ask the user for confirmation.
/// </summary>
public partial class ConfirmDialog : ComponentBase
{
[CascadingParameter]
private MudDialogInstance MudDialog { get; set; } = null!;
[Parameter]
public string Message { get; set; } = string.Empty;
private void Cancel() => this.MudDialog.Cancel();
private void Confirm() => this.MudDialog.Close(DialogResult.Ok(true));
}
@@ -1,17 +0,0 @@
namespace AIStudio.Components.CommonDialogs;
public static class DialogOptions
{
public static readonly MudBlazor.DialogOptions FULLSCREEN = new()
{
CloseOnEscapeKey = true,
FullWidth = true, MaxWidth = MaxWidth.Medium,
};
public static readonly MudBlazor.DialogOptions FULLSCREEN_NO_HEADER = new()
{
NoHeader = true,
CloseOnEscapeKey = true,
FullWidth = true, MaxWidth = MaxWidth.Medium,
};
}
@@ -1,10 +0,0 @@
<MudDialog>
<DialogContent>
<MudText Typo="Typo.body1">@this.Message</MudText>
<MudTextField T="string" @bind-Text="@this.UserInput" Variant="Variant.Outlined" AutoGrow="@false" Lines="1" Label="Chat name" AutoFocus="@true" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Cancel" Variant="Variant.Filled">Cancel</MudButton>
<MudButton OnClick="@this.Confirm" Variant="Variant.Filled" Color="@this.ConfirmColor">@this.ConfirmText</MudButton>
</DialogActions>
</MudDialog>
@@ -1,43 +0,0 @@
using AIStudio.Settings;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components.CommonDialogs;
public partial class SingleInputDialog : ComponentBase
{
[CascadingParameter]
private MudDialogInstance MudDialog { get; set; } = null!;
[Parameter]
public string Message { get; set; } = string.Empty;
[Parameter]
public string UserInput { get; set; } = string.Empty;
[Parameter]
public string ConfirmText { get; set; } = "OK";
[Parameter]
public Color ConfirmColor { get; set; } = Color.Error;
[Inject]
private SettingsManager SettingsManager { get; set; } = null!;
private static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new();
#region Overrides of ComponentBase
protected override async Task OnInitializedAsync()
{
// Configure the spellchecking for the user input:
this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES);
await base.OnInitializedAsync();
}
#endregion
private void Cancel() => this.MudDialog.Cancel();
private void Confirm() => this.MudDialog.Close(DialogResult.Ok(this.UserInput));
}
@@ -1,14 +0,0 @@
@using AIStudio.Tools
<MudDialog>
<DialogContent>
<MudText Typo="Typo.h4" Class="d-inline-flex align-center">
<MudIcon Icon="@Icons.Material.Filled.Update" Size="Size.Large" Class="mr-3"/>
Update from v@(META_DATA.Version) to v@(this.UpdateResponse.NewVersion)
</MudText>
<MudMarkdown Value="@this.UpdateResponse.Changelog" OverrideHeaderTypo="@Markdown.OverrideHeaderTypo"/>
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Cancel" Variant="Variant.Filled">Install later</MudButton>
<MudButton OnClick="@this.Confirm" Variant="Variant.Filled" Color="Color.Info">Install now</MudButton>
</DialogActions>
</MudDialog>
@@ -1,26 +0,0 @@
using System.Reflection;
using AIStudio.Tools;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components.CommonDialogs;
/// <summary>
/// The update dialog that is used to inform the user about an available update.
/// </summary>
public partial class UpdateDialog : ComponentBase
{
private static readonly Assembly ASSEMBLY = Assembly.GetExecutingAssembly();
private static readonly MetaDataAttribute META_DATA = ASSEMBLY.GetCustomAttribute<MetaDataAttribute>()!;
[CascadingParameter]
private MudDialogInstance MudDialog { get; set; } = null!;
[Parameter]
public UpdateResponse UpdateResponse { get; set; }
private void Cancel() => this.MudDialog.Cancel();
private void Confirm() => this.MudDialog.Close(DialogResult.Ok(true));
}
@@ -1,15 +0,0 @@
<MudDialog>
<DialogContent>
<MudText Typo="Typo.body1">@this.Message</MudText>
<MudList T="Guid" @bind-SelectedValue="@this.selectedWorkspace">
@foreach (var (workspaceName, workspaceId) in this.workspaces)
{
<MudListItem Text="@workspaceName" Icon="@Icons.Material.Filled.Description" Value="@workspaceId" />
}
</MudList>
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Cancel" Variant="Variant.Filled">Cancel</MudButton>
<MudButton OnClick="@this.Confirm" Variant="Variant.Filled" Color="Color.Info">@this.ConfirmText</MudButton>
</DialogActions>
</MudDialog>
@@ -1,60 +0,0 @@
using System.Text;
using AIStudio.Settings;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components.CommonDialogs;
public partial class WorkspaceSelectionDialog : ComponentBase
{
[CascadingParameter]
private MudDialogInstance MudDialog { get; set; } = null!;
[Parameter]
public string Message { get; set; } = string.Empty;
[Parameter]
public Guid SelectedWorkspace { get; set; } = Guid.Empty;
[Parameter]
public string ConfirmText { get; set; } = "OK";
private readonly Dictionary<string, Guid> workspaces = new();
private Guid selectedWorkspace;
#region Overrides of ComponentBase
protected override async Task OnInitializedAsync()
{
this.selectedWorkspace = this.SelectedWorkspace;
// Get the workspace root directory:
var workspaceDirectories = Path.Join(SettingsManager.DataDirectory, "workspaces");
if(!Directory.Exists(workspaceDirectories))
{
await base.OnInitializedAsync();
return;
}
// Enumerate the workspace directories:
foreach (var workspaceDirPath in Directory.EnumerateDirectories(workspaceDirectories))
{
// Read the `name` file:
var workspaceNamePath = Path.Join(workspaceDirPath, "name");
var workspaceName = await File.ReadAllTextAsync(workspaceNamePath, Encoding.UTF8);
// Add the workspace to the list:
this.workspaces.Add(workspaceName, Guid.Parse(Path.GetFileName(workspaceDirPath)));
}
this.StateHasChanged();
await base.OnInitializedAsync();
}
#endregion
private void Cancel() => this.MudDialog.Cancel();
private void Confirm() => this.MudDialog.Close(DialogResult.Ok(this.selectedWorkspace));
}
@@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components.Blocks;
namespace AIStudio.Components;
/// <summary>
/// Configuration component for any boolean option.
@@ -3,7 +3,7 @@ using AIStudio.Tools;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components.Blocks;
namespace AIStudio.Components;
public partial class ConfigurationProviderSelection : ComponentBase, IMessageBusReceiver, IDisposable
{
@@ -2,7 +2,7 @@ using AIStudio.Settings;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components.Blocks;
namespace AIStudio.Components;
/// <summary>
/// Configuration component for selecting a value from a list.
@@ -2,7 +2,7 @@ using System.Numerics;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components.Blocks;
namespace AIStudio.Components;
public partial class ConfigurationSlider<T> : ConfigurationBase where T : struct, INumber<T>
{
@@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components.Blocks;
namespace AIStudio.Components;
public partial class ConfigurationText : ConfigurationBase
{
@@ -2,7 +2,7 @@ using AIStudio.Settings;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components.Blocks;
namespace AIStudio.Components;
public partial class EnumSelection<T> : EnumSelectionBase where T : struct, Enum
{
@@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components.Blocks;
namespace AIStudio.Components;
public abstract class EnumSelectionBase : ComponentBase
{
@@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components.Blocks;
namespace AIStudio.Components;
public partial class ExpansionPanel : ComponentBase
{
@@ -0,0 +1,3 @@
namespace AIStudio.Components;
public interface ITreeItem;
@@ -1,9 +1,9 @@
using AIStudio.Components.Layout;
using AIStudio.Layout;
using AIStudio.Tools;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components.Blocks;
namespace AIStudio.Components;
public partial class InnerScrolling : MSGComponentBase
{
@@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components.Blocks;
namespace AIStudio.Components;
public partial class Issues : ComponentBase
{
@@ -1,58 +0,0 @@
@using AIStudio.Settings.DataModel
@inherits LayoutComponentBase
<MudPaper Height="calc(100vh);" Elevation="0">
<MudLayout>
@if (!this.performingUpdate)
{
<MudDrawerContainer Class="mud-height-full absolute">
<MudDrawer @bind-Open="@this.navBarOpen" MiniWidth="@NAVBAR_COLLAPSED_WIDTH" Width="@NAVBAR_EXPANDED_WIDTH" Elevation="1" Fixed="@true" Variant="@DrawerVariant.Mini" OpenMiniOnHover="@(this.SettingsManager.ConfigurationData.App.NavigationBehavior is NavBehavior.EXPAND_ON_HOVER)" Color="Color.Default">
<MudNavMenu>
@foreach (var navBarItem in NAV_ITEMS)
{
if (this.SettingsManager.ConfigurationData.App.NavigationBehavior is NavBehavior.NEVER_EXPAND_USE_TOOLTIPS)
{
<MudTooltip Text="@navBarItem.Name" Placement="Placement.Right">
<MudNavLink Href="@navBarItem.Path" Match="@(navBarItem.MatchAll ? NavLinkMatch.All : NavLinkMatch.Prefix)" Icon="@navBarItem.Icon" IconColor="@navBarItem.IconColor">@navBarItem.Name</MudNavLink>
</MudTooltip>
}
else
{
<MudNavLink Href="@navBarItem.Path" Match="@(navBarItem.MatchAll ? NavLinkMatch.All : NavLinkMatch.Prefix)" Icon="@navBarItem.Icon" IconColor="@navBarItem.IconColor">@navBarItem.Name</MudNavLink>
}
}
</MudNavMenu>
</MudDrawer>
</MudDrawerContainer>
}
<MudMainContent Class="mud-height-full pt-1" Style="@this.PaddingLeft">
<MudContainer Fixed="@true" Class="mud-height-full" Style="margin-left: 5em; width: calc(100% - 5em);">
@if (!this.performingUpdate && this.IsUpdateAlertVisible)
{
<MudAlert NoIcon="@true" Severity="Severity.Info" Variant="Variant.Filled" ShowCloseIcon="@true" Dense="@true" CloseIconClicked="() => this.DismissUpdate()" Class="mt-2 mb-2">
<div class="d-inline-flex align-center">
<MudIcon Icon="@Icons.Material.Filled.Update" Size="Size.Medium" Class="mr-3"/>
An update to version @this.updateToVersion is available.
<MudButton Variant="Variant.Filled" Color="Color.Dark" Size="Size.Small" Class="ml-3" OnClick="() => this.ShowUpdateDialog()">
Show details
</MudButton>
</div>
</MudAlert>
}
@if (!this.performingUpdate)
{
<CascadingValue Value="@this" IsFixed="true">
@this.Body
</CascadingValue>
}
<MudOverlay Visible="@this.performingUpdate" DarkBackground="@true" LockScroll="@true">
<MudText Typo="Typo.h3">Please wait for the update to complete...</MudText>
<MudProgressLinear Color="Color.Primary" Indeterminate="@true" Size="Size.Large" Rounded="@true"/>
</MudOverlay>
</MudContainer>
</MudMainContent>
</MudLayout>
</MudPaper>
@@ -1,224 +0,0 @@
using AIStudio.Components.CommonDialogs;
using AIStudio.Settings;
using AIStudio.Settings.DataModel;
using AIStudio.Tools;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Routing;
using DialogOptions = AIStudio.Components.CommonDialogs.DialogOptions;
namespace AIStudio.Components.Layout;
public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDisposable
{
[Inject]
private IJSRuntime JsRuntime { get; init; } = null!;
[Inject]
private SettingsManager SettingsManager { get; init; } = null!;
[Inject]
private MessageBus MessageBus { get; init; } = null!;
[Inject]
private IDialogService DialogService { get; init; } = null!;
[Inject]
private Rust Rust { get; init; } = null!;
[Inject]
private ISnackbar Snackbar { get; init; } = null!;
[Inject]
private NavigationManager NavigationManager { get; init; } = null!;
public string AdditionalHeight { get; private set; } = "0em";
private string PaddingLeft => this.navBarOpen ? $"padding-left: {NAVBAR_EXPANDED_WIDTH_INT - NAVBAR_COLLAPSED_WIDTH_INT}em;" : "padding-left: 0em;";
private const int NAVBAR_COLLAPSED_WIDTH_INT = 4;
private const int NAVBAR_EXPANDED_WIDTH_INT = 10;
private static readonly string NAVBAR_COLLAPSED_WIDTH = $"{NAVBAR_COLLAPSED_WIDTH_INT}em";
private static readonly string NAVBAR_EXPANDED_WIDTH = $"{NAVBAR_EXPANDED_WIDTH_INT}em";
private bool navBarOpen;
private bool isUpdateAvailable;
private bool performingUpdate;
private bool userDismissedUpdate;
private string updateToVersion = string.Empty;
private UpdateResponse? currentUpdateResponse;
private static readonly IReadOnlyCollection<NavBarItem> NAV_ITEMS = new List<NavBarItem>
{
new("Home", Icons.Material.Filled.Home, Color.Default, Pages.Path.HOME, true),
new("Chat", Icons.Material.Filled.Chat, Color.Default, Pages.Path.CHAT, false),
new("Assistants", Icons.Material.Filled.Apps, Color.Default, Pages.Path.ASSISTANTS, false),
new("Supporters", Icons.Material.Filled.Favorite, Color.Error, Pages.Path.SUPPORTERS, false),
new("About", Icons.Material.Filled.Info, Color.Default, Pages.Path.ABOUT, false),
new("Settings", Icons.Material.Filled.Settings, Color.Default, Pages.Path.SETTINGS, false),
};
#region Overrides of ComponentBase
protected override async Task OnInitializedAsync()
{
this.NavigationManager.RegisterLocationChangingHandler(this.OnLocationChanging);
//
// We use the Tauri API (Rust) to get the data and config directories
// for this app.
//
var dataDir = await this.JsRuntime.InvokeAsync<string>("window.__TAURI__.path.appLocalDataDir");
var configDir = await this.JsRuntime.InvokeAsync<string>("window.__TAURI__.path.appConfigDir");
// Store the directories in the settings manager:
SettingsManager.ConfigDirectory = configDir;
SettingsManager.DataDirectory = Path.Join(dataDir, "data");
Directory.CreateDirectory(SettingsManager.DataDirectory);
// Ensure that all settings are loaded:
await this.SettingsManager.LoadSettings();
// Register this component with the message bus:
this.MessageBus.RegisterComponent(this);
this.MessageBus.ApplyFilters(this, [], [ Event.UPDATE_AVAILABLE, Event.USER_SEARCH_FOR_UPDATE, Event.CONFIGURATION_CHANGED ]);
// Set the js runtime for the update service:
UpdateService.SetBlazorDependencies(this.JsRuntime, this.Snackbar);
TemporaryChatService.Initialize();
// Should the navigation bar be open by default?
if(this.SettingsManager.ConfigurationData.App.NavigationBehavior is NavBehavior.ALWAYS_EXPAND)
this.navBarOpen = true;
await base.OnInitializedAsync();
}
#endregion
#region Implementation of IMessageBusReceiver
public async Task ProcessMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data)
{
switch (triggeredEvent)
{
case Event.USER_SEARCH_FOR_UPDATE:
this.userDismissedUpdate = false;
break;
case Event.UPDATE_AVAILABLE:
if (data is UpdateResponse updateResponse)
{
this.currentUpdateResponse = updateResponse;
this.isUpdateAvailable = updateResponse.UpdateIsAvailable;
this.updateToVersion = updateResponse.NewVersion;
await this.InvokeAsync(this.StateHasChanged);
await this.SendMessage<bool>(Event.STATE_HAS_CHANGED);
}
break;
case Event.CONFIGURATION_CHANGED:
if(this.SettingsManager.ConfigurationData.App.NavigationBehavior is NavBehavior.ALWAYS_EXPAND)
this.navBarOpen = true;
else
this.navBarOpen = false;
this.StateHasChanged();
break;
}
}
public Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data)
{
return Task.FromResult<TResult?>(default);
}
#endregion
private async Task DismissUpdate()
{
this.userDismissedUpdate = true;
this.AdditionalHeight = "0em";
await this.SendMessage<bool>(Event.STATE_HAS_CHANGED);
}
private bool IsUpdateAlertVisible
{
get
{
var state = this.isUpdateAvailable && !this.userDismissedUpdate;
this.AdditionalHeight = state ? "3em" : "0em";
return state;
}
}
private async Task ShowUpdateDialog()
{
if(this.currentUpdateResponse is null)
return;
//
// Replace the fir line with `# Changelog`:
//
var changelog = this.currentUpdateResponse.Value.Changelog;
if (!string.IsNullOrWhiteSpace(changelog))
{
var lines = changelog.Split('\n');
if (lines.Length > 0)
lines[0] = "# Changelog";
changelog = string.Join('\n', lines);
}
var updatedResponse = this.currentUpdateResponse.Value with { Changelog = changelog };
var dialogParameters = new DialogParameters<UpdateDialog>
{
{ x => x.UpdateResponse, updatedResponse }
};
var dialogReference = await this.DialogService.ShowAsync<UpdateDialog>("Update", dialogParameters, DialogOptions.FULLSCREEN_NO_HEADER);
var dialogResult = await dialogReference.Result;
if (dialogResult is null || dialogResult.Canceled)
return;
this.performingUpdate = true;
this.StateHasChanged();
await this.Rust.InstallUpdate(this.JsRuntime);
}
private async ValueTask OnLocationChanging(LocationChangingContext context)
{
if (await MessageBus.INSTANCE.SendMessageUseFirstResult<bool, bool>(this, Event.HAS_CHAT_UNSAVED_CHANGES))
{
var dialogParameters = new DialogParameters
{
{ "Message", "Are you sure you want to leave the chat page? All unsaved changes will be lost." },
};
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Leave Chat Page", dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result;
if (dialogResult is null || dialogResult.Canceled)
{
context.PreventNavigation();
return;
}
// User accepted to leave the chat page, reset the chat state:
await MessageBus.INSTANCE.SendMessage<bool>(this, Event.RESET_CHAT_STATE);
}
}
#region Implementation of IDisposable
public void Dispose()
{
this.MessageBus.Unregister(this);
}
#endregion
}
@@ -1,3 +0,0 @@
namespace AIStudio.Components.Layout;
public record NavBarItem(string Name, string Icon, Color IconColor, string Path, bool MatchAll);
@@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components.Blocks;
namespace AIStudio.Components;
public partial class Motivation : ComponentBase
{
@@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components.Blocks;
namespace AIStudio.Components;
public partial class MudTextList : ComponentBase
{
@@ -2,7 +2,7 @@ using System.Numerics;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components.Blocks;
namespace AIStudio.Components;
public partial class MudTextSlider<T> : ComponentBase where T : struct, INumber<T>
{
@@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components.Blocks;
namespace AIStudio.Components;
public partial class MudTextSwitch : ComponentBase
{
@@ -1,59 +0,0 @@
@attribute [Route(Path.ABOUT)]
@using AIStudio.Tools
<MudText Typo="Typo.h3" Class="mb-2">About MindWork AI Studio</MudText>
<InnerScrolling HeaderHeight="6em">
<MudExpansionPanels Class="mb-3" MultiExpansion="@false">
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Layers" HeaderText="Versions" IsExpanded="@true">
<MudText>
The following list shows the versions of the MindWork AI Studio, the used compilers, build time, etc.:
</MudText>
<MudList T="string" Class="mb-3">
<MudListItem T="string" Icon="@Icons.Material.Outlined.Chat" Text="@VersionApp"/>
<MudListItem T="string" Icon="@Icons.Material.Outlined.Timer" Text="@BuildTime"/>
<MudListItem T="string" Icon="@Icons.Material.Outlined.Build" Text="@VersionDotnetSdk"/>
<MudListItem T="string" Icon="@Icons.Material.Outlined.Memory" Text="@VersionDotnetRuntime"/>
<MudListItem T="string" Icon="@Icons.Material.Outlined.Build" Text="@VersionRust"/>
<MudListItem T="string" Icon="@Icons.Material.Outlined.Widgets" Text="@MudBlazorVersion"/>
<MudListItem T="string" Icon="@Icons.Material.Outlined.Memory" Text="@TauriVersion"/>
</MudList>
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Update" OnClick="() => this.CheckForUpdate()">
Check for updates
</MudButton>
</ExpansionPanel>
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.EventNote" HeaderText="Changelog">
<Changelog/>
</ExpansionPanel>
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Engineering" HeaderText="Motivation">
<Motivation/>
</ExpansionPanel>
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Lightbulb" HeaderText="Vision">
<Vision/>
</ExpansionPanel>
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.AutoAwesomeMotion" HeaderText="Used Open Source Projects">
<MudGrid Spacing="1">
<ThirdPartyComponent Name=".NET" Developer="Microsoft & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/dotnet/runtime/blob/main/LICENSE.TXT" RepositoryUrl="https://github.com/dotnet" UseCase="The C# language is used for the implementation of the user interface and the backend. To implement the user interface with C#, the Blazor technology from ASP.NET Core is used. All these technologies are integrated into the .NET SDK."/>
<ThirdPartyComponent Name="MudBlazor" Developer="Jonny Larsson, Meinrad Recheis & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/MudBlazor/MudBlazor/blob/dev/LICENSE" RepositoryUrl="https://github.com/MudBlazor/MudBlazor/" UseCase="Building on .NET, ASP.NET Core, and Blazor, MudBlazor is used as a library for designing and developing the user interface. It is a great project that significantly accelerates the development of advanced user interfaces with Blazor."/>
<ThirdPartyComponent Name="MudBlazor.Markdown" Developer="My Nihongo & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/MyNihongo/MudBlazor.Markdown/blob/main/LICENSE" RepositoryUrl="https://github.com/MyNihongo/MudBlazor.Markdown" UseCase="This component is used to render Markdown text. This is important because the LLM often responds with Markdown-formatted text, allowing us to present it in a way that is easier to read."/>
<ThirdPartyComponent Name="Rust" Developer="Graydon Hoare, Rust Foundation & Open Source Community" LicenseName="MIT & Apache-2.0" LicenseUrl="https://github.com/rust-lang/rust?tab=readme-ov-file#license" RepositoryUrl="https://github.com/rust-lang/rust" UseCase="The .NET backend cannot be started as a desktop app. Therefore, I use a second backend in Rust, which I call runtime. With Rust as the runtime, Tauri can be used to realize a typical desktop app. Thanks to Rust, this app can be offered for Windows, macOS, and Linux desktops. Rust is a great language for developing safe and high-performance software."/>
<ThirdPartyComponent Name="Tauri" Developer="Daniel Thompson-Yvetot, Lucas Nogueira, Tensor, Boscop, Serge Zaitsev, George Burton & Open Source Community" LicenseName="MIT & Apache-2.0" LicenseUrl="https://github.com/tauri-apps/tauri?tab=readme-ov-file#licenses" RepositoryUrl="https://github.com/tauri-apps/tauri" UseCase="Tauri is used to host the Blazor user interface. It is a great project that allows the creation of desktop applications using web technologies. I love Tauri!"/>
<ThirdPartyComponent Name="serde" Developer="Erick Tryzelaar, David Tolnay & Open Source Community" LicenseName="MIT & Apache-2.0" LicenseUrl="https://github.com/serde-rs/serde?tab=readme-ov-file#license" RepositoryUrl="https://github.com/serde-rs/serde" UseCase="Now we have multiple systems, some developed in .NET and others in Rust. The data format JSON is responsible for translating data between both worlds (called data serialization and deserialization). Serde takes on this task in the Rust world. The counterpart in the .NET world is an integral part of .NET and is located in System.Text.Json."/>
<ThirdPartyComponent Name="keyring" Developer="Walther Chen, Daniel Brotsky & Open Source Community" LicenseName="MIT & Apache-2.0" LicenseUrl="https://github.com/hwchen/keyring-rs?tab=readme-ov-file#license" RepositoryUrl="https://github.com/hwchen/keyring-rs" UseCase="In order to use any LLM, each user must store their so-called token for each LLM provider. This token must be kept secure, similar to a password. The safest way to do this is offered by operating systems like macOS, Windows, and Linux: They have mechanisms to store such data, if available, on special security hardware. Since this is currently not possible in .NET, we use this Rust library."/>
<ThirdPartyComponent Name="arboard" Developer="Artur Kovacs, Avi Weinstock, 1Password & Open Source Community" LicenseName="MIT & Apache-2.0" LicenseUrl="https://github.com/1Password/arboard" RepositoryUrl="https://github.com/1Password/arboard" UseCase="To be able to use the responses of the LLM in other apps, we often use the clipboard of the respective operating system. Unfortunately, in .NET there is no solution that works with all operating systems. Therefore, I have opted for this library in Rust. This way, data transfer to other apps works on every system."/>
<ThirdPartyComponent Name="tokio" Developer="Alex Crichton & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/tokio-rs/tokio/blob/master/LICENSE" RepositoryUrl="https://github.com/tokio-rs/tokio" UseCase="Code in the Rust language can be specified as synchronous or asynchronous. Unlike .NET and the C# language, Rust cannot execute asynchronous code by itself. Rust requires support in the form of an executor for this. Tokio is one such executor."/>
<ThirdPartyComponent Name="flexi_logger" Developer="emabee & Open Source Community" LicenseName="MIT & Apache-2.0" LicenseUrl="https://github.com/emabee/flexi_logger" RepositoryUrl="https://github.com/emabee/flexi_logger" UseCase="This Rust library is used to output the app's messages to the terminal. This is helpful during development and troubleshooting. This feature is initially invisible; when the app is started via the terminal, the messages become visible."/>
<ThirdPartyComponent Name="HtmlAgilityPack" Developer="ZZZ Projects & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/zzzprojects/html-agility-pack/blob/master/LICENSE" RepositoryUrl="https://github.com/zzzprojects/html-agility-pack" UseCase="We use the HtmlAgilityPack to extract content from the web. This is necessary, e.g., when you provide a URL as input for an assistant."/>
<ThirdPartyComponent Name="ReverseMarkdown" Developer="Babu Annamalai & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/mysticmind/reversemarkdown-net/blob/master/LICENSE" RepositoryUrl="https://github.com/mysticmind/reversemarkdown-net" UseCase="This library is used to convert HTML to Markdown. This is necessary, e.g., when you provide a URL as input for an assistant."/>
<ThirdPartyComponent Name="wikEd diff" Developer="Cacycle & Open Source Community" LicenseName="None (public domain)" LicenseUrl="https://en.wikipedia.org/wiki/User:Cacycle/diff#License" RepositoryUrl="https://en.wikipedia.org/wiki/User:Cacycle/diff" UseCase="This library is used to display the differences between two texts. This is necessary, e.g., for the grammar and spelling assistant."/>
</MudGrid>
</ExpansionPanel>
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Verified" HeaderText="License: FSL-1.1-MIT">
<MudMarkdown Value="@LICENSE" OverrideHeaderTypo="@Markdown.OverrideHeaderTypo"/>
</ExpansionPanel>
</MudExpansionPanels>
</InnerScrolling>
@@ -1,148 +0,0 @@
using System.Reflection;
using AIStudio.Tools;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components.Pages;
public partial class About : ComponentBase
{
[Inject]
private MessageBus MessageBus { get; init; } = null!;
private static readonly Assembly ASSEMBLY = Assembly.GetExecutingAssembly();
private static readonly MetaDataAttribute META_DATA = ASSEMBLY.GetCustomAttribute<MetaDataAttribute>()!;
private static string VersionDotnetRuntime => $"Used .NET runtime: v{META_DATA.DotnetVersion}";
private static string VersionDotnetSdk => $"Used .NET SDK: v{META_DATA.DotnetSdkVersion}";
private static string VersionRust => $"Used Rust compiler: v{META_DATA.RustVersion}";
private static string VersionApp => $"MindWork AI Studio: v{META_DATA.Version} (commit {META_DATA.AppCommitHash}, build {META_DATA.BuildNum})";
private static string BuildTime => $"Build time: {META_DATA.BuildTime}";
private static string MudBlazorVersion => $"MudBlazor: v{META_DATA.MudBlazorVersion}";
private static string TauriVersion => $"Tauri: v{META_DATA.TauriVersion}";
private const string LICENSE = """
# Functional Source License, Version 1.1, MIT Future License
## Abbreviation
FSL-1.1-MIT
## Notice
Copyright 2024 Thorsten Sommer
## Terms and Conditions
### Licensor ("We")
The party offering the Software under these Terms and Conditions.
### The Software
The "Software" is each version of the software that we make available under
these Terms and Conditions, as indicated by our inclusion of these Terms and
Conditions with the Software.
### License Grant
Subject to your compliance with this License Grant and the Patents,
Redistribution and Trademark clauses below, we hereby grant you the right to
use, copy, modify, create derivative works, publicly perform, publicly display
and redistribute the Software for any Permitted Purpose identified below.
### Permitted Purpose
A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
means making the Software available to others in a commercial product or
service that:
1. substitutes for the Software;
2. substitutes for any other product or service we offer using the Software
that exists as of the date we make the Software available; or
3. offers the same or substantially similar functionality as the Software.
Permitted Purposes specifically include using the Software:
1. for your internal use and access;
2. for non-commercial education;
3. for non-commercial research; and
4. in connection with professional services that you provide to a licensee
using the Software in accordance with these Terms and Conditions.
### Patents
To the extent your use for a Permitted Purpose would necessarily infringe our
patents, the license grant above includes a license under our patents. If you
make a claim against any party that the Software infringes or contributes to
the infringement of any patent, then your patent license to the Software ends
immediately.
### Redistribution
The Terms and Conditions apply to all copies, modifications and derivatives of
the Software.
If you redistribute any copies, modifications or derivatives of the Software,
you must include a copy of or a link to these Terms and Conditions and not
remove any copyright notices provided in or with the Software.
### Disclaimer
THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
### Trademarks
Except for displaying the License Details and identifying us as the origin of
the Software, you have no right under these Terms and Conditions to use our
trademarks, trade names, service marks or product names.
## Grant of Future License
We hereby irrevocably grant you an additional license to use the Software under
the MIT license that is effective on the second anniversary of the date we make
the Software available. On or after that date, you may use the Software under
the MIT license, in which case the following will apply:
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
""";
private async Task CheckForUpdate()
{
await this.MessageBus.SendMessage<bool>(this, Event.USER_SEARCH_FOR_UPDATE);
}
}
@@ -1,56 +0,0 @@
@attribute [Route(Path.ASSISTANT_AGENDA)]
@using AIStudio.Tools
@inherits AssistantBaseCore
<MudTextField T="string" @bind-Text="@this.inputName" Validation="@this.ValidateName" Label="Meeting Name" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Tag" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="Name the meeting, seminar, etc." Placeholder="Weekly jour fixe" Class="mb-3"/>
<MudTextField T="string" @bind-Text="@this.inputTopic" Validation="@this.ValidateTopic" Label="Topic" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.EventNote" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="Describe the topic of the meeting, seminar, etc. Is it about quantum computing, software engineering, or is it a general business meeting?" Placeholder="Project meeting" Class="mb-3"/>
<MudTextField T="string" @bind-Text="@this.inputContent" Validation="@this.ValidateContent" Label="Content list" Variant="Variant.Outlined" Lines="6" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="Bullet list the content of the meeting, seminar, etc. roughly. Use dashes (-) to separate the items." Placeholder="@PLACEHOLDER_CONTENT" Class="mb-3" Immediate="@false" DebounceInterval="1_000" OnDebounceIntervalElapsed="@this.OnContentChanged" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.ListAlt"/>
<MudSelect T="string" Label="(Optional) What topics should be the focus?" MultiSelection="@true" @bind-SelectedValues="@this.selectedFoci" Variant="Variant.Outlined" Class="mb-3" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.ListAlt">
@foreach (var contentLine in this.contentLines)
{
@if(!this.justBriefly.Contains(contentLine))
{
<MudSelectItem T="string" Value="@contentLine">@contentLine</MudSelectItem>
}
}
</MudSelect>
<MudSelect T="string" Label="(Optional) What topics should only be briefly addressed?" MultiSelection="@true" @bind-SelectedValues="@this.justBriefly" Variant="Variant.Outlined" Class="mb-3" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.ListAlt">
@foreach (var contentLine in this.contentLines)
{
@if(!this.selectedFoci.Contains(contentLine))
{
<MudSelectItem T="string" Value="@contentLine">@contentLine</MudSelectItem>
}
}
</MudSelect>
<MudTextField T="string" @bind-Text="@this.inputObjective" Validation="@this.ValidateObjective" Label="Objective" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Flag" Variant="Variant.Outlined" Lines="3" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="Describe the objective(s) of the meeting, seminar, etc. What should be achieved?" Placeholder="Discuss the current project status and plan the next steps." Class="mb-3"/>
<MudTextField T="string" @bind-Text="@this.inputModerator" Validation="@this.ValidateModerator" Label="Moderator" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Person3" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="Who will moderate the meeting, seminar, etc.?" Placeholder="Jane Doe" Class="mb-3" />
<MudTextField T="string" @bind-Text="@this.inputDuration" Validation="@this.ValidateDuration" Label="Duration" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Schedule" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="How long will the meeting, seminar, etc. last? E.g., '2 hours', or '2 days (first day 8 hours, then 4 hours)', etc." Placeholder="2 hours" Class="mb-3"/>
<MudTextField T="string" @bind-Text="@this.inputStartTime" Validation="@this.ValidateStartTime" Label="Start time" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Schedule" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="When will the meeting, seminar, etc. start? E.g., '9:00 AM', or '9:00 AM (CET)', etc. When the meeting is a multi-day event, specify the start time for each day." Placeholder="9:00 AM" Class="mb-3"/>
<MudTextField T="string" @bind-Text="@this.inputWhoIsPresenting" Label="(Optional) Who is presenting?" Variant="Variant.Outlined" Lines="6" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="(Optional) List the persons who will present at the meeting, seminar, etc. Use dashes (-) to separate the items." Placeholder="@PLACEHOLDER_WHO_IS_PRESENTING" Class="mb-3"/>
<MudTextSwitch Label="Do the participants need to get to know each other first?" @bind-Value="@this.introduceParticipants" LabelOn="Yes, introduce participants" LabelOff="No, participants know each other" />
<EnumSelection T="NumberParticipants" @bind-Value="@this.numberParticipants" NameFunc="@(participants => participants.Name())" Icon="@Icons.Material.Filled.Group" Label="Number of participants" ValidateSelection="@this.ValidateNumberParticipants"/>
<MudTextSwitch Label="Should the participants be involved passively or actively?" @bind-Value="@this.activeParticipation" LabelOn="Active participation, like world café, discussions, etc." LabelOff="Passive participation, like presentations, lectures, etc." />
<MudTextSwitch Label="Is this a virtual event, e.g., a call or webinar?" @bind-Value="@this.isMeetingVirtual" LabelOn="Yes, this is a virtual event" LabelOff="No, this is a physical event" />
@if (!this.isMeetingVirtual)
{
<MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg">
<MudTextField T="string" @bind-Text="@this.inputLocation" Validation="@this.ValidateLocation" Label="Location" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.MyLocation" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="Where will the meeting, seminar, etc. take place?" Placeholder="Hamburg, Germany" Class="mb-3"/>
<MudTextSwitch Label="Should there be a joint dinner?" @bind-Value="@this.goingToDinner" LabelOn="Yes, there should be a joint dinner" LabelOff="No, there should be no joint dinner" />
<MudTextSwitch Label="Should there be a social activity?" @bind-Value="@this.doingSocialActivity" LabelOn="Yes, there should be a social activity" LabelOff="No, there should be no social activity" />
<MudTextSwitch Label="Do participants need to arrive and depart?" @bind-Value="@this.needToArriveAndDepart" LabelOn="Yes, participants need to arrive and depart" LabelOff="No, participants do not need to arrive and depart" />
<MudStack Row="@true" Wrap="Wrap.Wrap">
<MudTextSlider T="int" Label="Approx. duration of the lunch break" @bind-Value="@this.durationLunchBreak" Min="30" Max="120" Step="5" Unit="minutes"/>
<MudTextSlider T="int" Label="Approx. duration of the coffee or tea breaks" @bind-Value="@this.durationBreaks" Min="10" Max="60" Step="5" Unit="minutes"/>
</MudStack>
</MudPaper>
}
<EnumSelection T="CommonLanguages" NameFunc="@(language => language.NameSelecting())" @bind-Value="@this.selectedTargetLanguage" ValidateSelection="@this.ValidateTargetLanguage" Icon="@Icons.Material.Filled.Translate" Label="Target language" AllowOther="@true" OtherValue="CommonLanguages.OTHER" @bind-OtherInput="@this.customTargetLanguage" ValidateOther="@this.ValidateCustomLanguage" LabelOther="Custom target language" />
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
<MudButton Variant="Variant.Filled" Class="mb-3" OnClick="() => this.CreateAgenda()">
Create agenda
</MudButton>
@@ -1,507 +0,0 @@
using System.Text;
using AIStudio.Chat;
using AIStudio.Tools;
namespace AIStudio.Components.Pages.Agenda;
public partial class AssistantAgenda : AssistantBaseCore
{
protected override string Title => "Agenda Planner";
protected override string Description =>
"""
This agenda planner helps you create a structured agenda for your meeting or seminar. Just provide some basic
information about the event, and the assistant will generate an agenda for you. You can also specify the
duration, the start time, the location, the target language, and other details.
""";
protected override string SystemPrompt =>
$"""
You are a friendly assistant who supports the planning and consultation of events. You receive
metadata about an event and create an agenda from it. Additionally, you provide advice on what
the organizer and moderator should consider. When creating the agenda, you take into account that
time for questions and discussions needs to be scheduled. For large events, you take into account
the logistical challenges that come with an increasing number of participants.
If the user is planning interactions with participants, you know various methods. For example:
- **Townhall Meeting**: An open assembly where participants can ask questions and provide feedback directly to leaders or speakers. Good moderation is necessary to queue contributions and hand the floor to the respective person. Additionally, one person should take notes visible to all. Ideally, this should not be the moderator.
- **Speed Dating**: Short, structured interaction rounds where participants get the chance to quickly introduce themselves and make initial connections. A long row of tables is needed so that two people can sit opposite each other. A moderator with a stopwatch must ensure that participants rotate and change seats every 3 to 5 minutes.
- **World Café**: Participants discuss in small groups at tables and rotate after set intervals to exchange thoughts and develop ideas further. Each table needs a host. Notes should be taken on a flipchart or digitally at each table. At the end, the table hosts could present their insights to all participants in a plenary session.
- **Fishbowl**: A group discusses in an inner circle while a larger group listens from the outside. Occasionally, listeners can move into the inner circle to actively participate.
- **BarCamp (Unconference)**: Participants contribute to part of the agenda themselves and can act as both presenters and listeners. Anyone can propose and lead a session. However, there should be initial moderation to guide this meta-process.
- **Open Space Technology**: A method for large groups where participants bring up their own topics and then work intensively in small groups. At the end, the groups can present their findings to all participants in a plenary session.
- **Panel Discussion**: A group of experts discusses a specific topic in front of an audience, followed by a question-and-answer session.
- **Breakout Sessions**: Larger groups are divided into smaller, topic-related groups to discuss specific issues more deeply and interactively.
- **Interactive Polling**: Real-time surveys during the meeting or seminar using digital technology to collect immediate feedback from participants. With an increasing number of participants, technical requirements also become more complex: not every Wi-Fi router can handle several hundred or thousand devices. Additionally, the moderator or speaker must have a stable network connection to display the results. It would be problematic if the speaker depends on the same Wi-Fi as the participants: if the Wi-Fi crashes due to the high number of participants, the speaker cannot show the results. Therefore, a LAN for the speaker or the use of a mobile hotspot would be a better choice.
- **Peer Learning Groups**: Small groups of participants work together to learn from each other and collaboratively develop solutions for specific challenges.
- **Role Play/Simulation**: Participants take on roles to simulate certain scenarios, gaining practical experience and promoting dynamic discussions.
- **Round Table Discussions**: Small group discussions on specific topics, often without formal moderation, to encourage open and equal dialogue.
- **Mind Mapping**: A method for visualizing ideas and concepts, which can foster creative exchange within groups.
- **Case Study Analysis**: Groups analyze real or hypothetical case studies and present their solutions or insights.
- **Silent Meeting/Writing**: Participants write down their thoughts and questions, which are then collected and discussed to balance out loud or dominant voices.
- **Debate**: Participants argue for or against a specific topic, often with a moderator who ensures that the discussion remains constructive.
- **Workshops**: Interactive sessions where participants work together on specific tasks or challenges.
- **Brainstorming**: A method for generating ideas in a group setting, often with a focus on quantity and creativity.
- **Design Thinking**: A structured approach to innovation solutions. Design thinking is structured in six phases: understand, observe, point of view, ideate, prototype, and test. Depending on the number of participants, there are two moderates needed. One moderator leads the process, the other supports the participants in their work. Design thinking may takes up to two days.
You output the agenda in the following Markdown format:
# [Agenda Title]
- 09:00 - 09:05 (5 minutes): Welcome
- 09:05 - 09:15 (10 minutes): Introduction
- ...
{this.SystemPromptSocialActivity()}
{this.SystemPromptDinner()}
# Organizational notes
[Your advice for the organizer goes here, like room setup, technical requirements, etc.]
# Moderation notes
[Your advice for the moderator goes here]
Output the agenda in the following language: {this.PromptLanguage()}.
""";
private const string PLACEHOLDER_CONTENT = """
- Project status
- We need to discuss how to proceed
- Problem solving in work package 3 possible?
- Concerns of the members
""";
private const string PLACEHOLDER_WHO_IS_PRESENTING = """
- John Doe: Project status
- Mary Jane: Work package 3
""";
protected override IReadOnlyList<IButtonData> FooterButtons =>
[
new SendToButton
{
Self = SendTo.AGENDA_ASSISTANT,
},
];
protected override ChatThread ConvertToChatThread => (this.chatThread ?? new()) with
{
SystemPrompt = SystemPrompts.DEFAULT,
};
protected override void ResetFrom()
{
this.inputContent = string.Empty;
this.contentLines.Clear();
this.selectedFoci = [];
this.justBriefly = [];
this.inputWhoIsPresenting = string.Empty;
if (!this.MightPreselectValues())
{
this.inputTopic = string.Empty;
this.inputName = string.Empty;
this.inputDuration = string.Empty;
this.inputStartTime = string.Empty;
this.inputObjective = string.Empty;
this.inputModerator = string.Empty;
this.selectedTargetLanguage = CommonLanguages.AS_IS;
this.customTargetLanguage = string.Empty;
this.introduceParticipants = false;
this.isMeetingVirtual = true;
this.inputLocation = string.Empty;
this.goingToDinner = false;
this.doingSocialActivity = false;
this.needToArriveAndDepart = false;
this.durationLunchBreak = 0;
this.durationBreaks = 0;
this.activeParticipation = false;
this.numberParticipants = NumberParticipants.NOT_SPECIFIED;
}
}
protected override bool MightPreselectValues()
{
if (this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)
{
this.inputTopic = this.SettingsManager.ConfigurationData.Agenda.PreselectTopic;
this.inputName = this.SettingsManager.ConfigurationData.Agenda.PreselectName;
this.inputDuration = this.SettingsManager.ConfigurationData.Agenda.PreselectDuration;
this.inputStartTime = this.SettingsManager.ConfigurationData.Agenda.PreselectStartTime;
this.inputObjective = this.SettingsManager.ConfigurationData.Agenda.PreselectObjective;
this.inputModerator = this.SettingsManager.ConfigurationData.Agenda.PreselectModerator;
this.selectedTargetLanguage = this.SettingsManager.ConfigurationData.Agenda.PreselectedTargetLanguage;
this.customTargetLanguage = this.SettingsManager.ConfigurationData.Agenda.PreselectedOtherLanguage;
this.introduceParticipants = this.SettingsManager.ConfigurationData.Agenda.PreselectIntroduceParticipants;
this.isMeetingVirtual = this.SettingsManager.ConfigurationData.Agenda.PreselectIsMeetingVirtual;
this.inputLocation = this.SettingsManager.ConfigurationData.Agenda.PreselectLocation;
this.goingToDinner = this.SettingsManager.ConfigurationData.Agenda.PreselectJointDinner;
this.doingSocialActivity = this.SettingsManager.ConfigurationData.Agenda.PreselectSocialActivity;
this.needToArriveAndDepart = this.SettingsManager.ConfigurationData.Agenda.PreselectArriveAndDepart;
this.durationLunchBreak = this.SettingsManager.ConfigurationData.Agenda.PreselectLunchTime;
this.durationBreaks = this.SettingsManager.ConfigurationData.Agenda.PreselectBreakTime;
this.activeParticipation = this.SettingsManager.ConfigurationData.Agenda.PreselectActiveParticipation;
this.numberParticipants = this.SettingsManager.ConfigurationData.Agenda.PreselectNumberParticipants;
this.providerSettings = this.SettingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == this.SettingsManager.ConfigurationData.Agenda.PreselectedProvider);
return true;
}
return false;
}
private string inputTopic = string.Empty;
private string inputName = string.Empty;
private string inputContent = string.Empty;
private string inputDuration = string.Empty;
private string inputStartTime = string.Empty;
private IEnumerable<string> selectedFoci = new HashSet<string>();
private IEnumerable<string> justBriefly = new HashSet<string>();
private string inputObjective = string.Empty;
private string inputModerator = string.Empty;
private CommonLanguages selectedTargetLanguage;
private string customTargetLanguage = string.Empty;
private bool introduceParticipants;
private bool isMeetingVirtual = true;
private string inputLocation = string.Empty;
private bool goingToDinner;
private bool doingSocialActivity;
private bool needToArriveAndDepart;
private int durationLunchBreak;
private int durationBreaks;
private bool activeParticipation;
private NumberParticipants numberParticipants;
private string inputWhoIsPresenting = string.Empty;
private readonly List<string> contentLines = [];
#region Overrides of ComponentBase
protected override async Task OnInitializedAsync()
{
this.MightPreselectValues();
var deferredContent = MessageBus.INSTANCE.CheckDeferredMessages<string>(Event.SEND_TO_AGENDA_ASSISTANT).FirstOrDefault();
if (deferredContent is not null)
this.inputContent = deferredContent;
await base.OnInitializedAsync();
}
#endregion
private void OnContentChanged(string content)
{
var previousSelectedFoci = new HashSet<string>();
var previousJustBriefly = new HashSet<string>();
this.contentLines.Clear();
foreach (var line in content.AsSpan().EnumerateLines())
{
var trimmedLine = line.Trim();
if (trimmedLine.StartsWith("-"))
trimmedLine = trimmedLine[1..].Trim();
if (trimmedLine.Length == 0)
continue;
var finalLine = trimmedLine.ToString();
if(this.selectedFoci.Any(x => x.StartsWith(finalLine, StringComparison.InvariantCultureIgnoreCase)))
previousSelectedFoci.Add(finalLine);
if(this.justBriefly.Any(x => x.StartsWith(finalLine, StringComparison.InvariantCultureIgnoreCase)))
previousJustBriefly.Add(finalLine);
this.contentLines.Add(finalLine);
}
this.selectedFoci = previousSelectedFoci;
this.justBriefly = previousJustBriefly;
}
private string? ValidateLocation(string location)
{
if(!this.isMeetingVirtual && string.IsNullOrWhiteSpace(location))
return "Please provide a location for the meeting or the seminar.";
return null;
}
private string? ValidateNumberParticipants(NumberParticipants selectedSize)
{
if(selectedSize is NumberParticipants.NOT_SPECIFIED)
return "Please select the number of participants.";
return null;
}
private string? ValidateTargetLanguage(CommonLanguages language)
{
if(language is CommonLanguages.AS_IS)
return "Please select a target language for the agenda.";
return null;
}
private string? ValidateDuration(string duration)
{
if(string.IsNullOrWhiteSpace(duration))
return "Please provide a duration for the meeting or the seminar, e.g. '2 hours', or '2 days (8 hours and 4 hours)', etc.";
return null;
}
private string? ValidateStartTime(string startTime)
{
if(string.IsNullOrWhiteSpace(startTime))
return "Please provide a start time for the meeting or the seminar. When the meeting is a multi-day event, specify the start time for each day, e.g. '9:00 AM, 10:00 AM', etc.";
return null;
}
private string? ValidateCustomLanguage(string language)
{
if(this.selectedTargetLanguage == CommonLanguages.OTHER && string.IsNullOrWhiteSpace(language))
return "Please provide a custom language.";
return null;
}
private string? ValidateTopic(string topic)
{
if(string.IsNullOrWhiteSpace(topic))
return "Please provide a topic for the agenda. What is the meeting or the seminar about?";
return null;
}
private string? ValidateName(string name)
{
if(string.IsNullOrWhiteSpace(name))
return "Please provide a name for the meeting or the seminar.";
return null;
}
private string? ValidateContent(string content)
{
if(string.IsNullOrWhiteSpace(content))
return "Please provide some content for the agenda. What are the main points of the meeting or the seminar?";
var lines = content.Split('\n');
foreach (var line in lines)
if(!line.TrimStart().StartsWith('-'))
return "Please start each line of your content list with a dash (-) to create a bullet point list.";
return null;
}
private string? ValidateObjective(string objective)
{
if(string.IsNullOrWhiteSpace(objective))
return "Please provide an objective for the meeting or the seminar. What do you want to achieve?";
return null;
}
private string? ValidateModerator(string moderator)
{
if(string.IsNullOrWhiteSpace(moderator))
return "Please provide a moderator for the meeting or the seminar. Who will lead the discussion?";
return null;
}
private async Task CreateAgenda()
{
await this.form!.Validate();
if (!this.inputIsValid)
return;
this.CreateChatThread();
var time = this.AddUserRequest(
$"""
Create an agenda for the meeting or seminar wit the title '{this.inputName}' using the following details:
- The overall topic is '{this.inputTopic}'.
- The objective of the meeting is '{this.inputObjective}'.
- The moderator is '{this.inputModerator}'.
- The planned duration is '{this.inputDuration}'.
- The planned start time is '{this.inputStartTime}'.
{this.PromptGettingToKnowEachOther()}
- The number of participants is '{this.numberParticipants.Name()}'.
{this.PromptActiveParticipation()}
{this.PromptVirtualMeeting()}
{this.PromptPhysicalMeeting()}
Here is draft what was planned for the meeting:
{this.inputContent}
Please do not follow this draft strictly. It is just a suggestion.
{this.PromptFoci()}
{this.PromptBriefly()}
{this.PromptWhoIsPresenting()}
""");
await this.AddAIResponseAsync(time);
}
private string PromptLanguage()
{
if(this.selectedTargetLanguage is CommonLanguages.OTHER)
return this.customTargetLanguage;
return this.selectedTargetLanguage.Name();
}
private string PromptPhysicalMeeting()
{
if(this.isMeetingVirtual)
return string.Empty;
return $"""
- The meeting takes place at the following location: '{this.inputLocation}'.
{this.PromptJoiningDinner()}
{this.PromptSocialActivity()}
{this.PromptArriveAndDepart()}
- In case a lunch break is necessary, the break should about {this.durationLunchBreak} minutes long.
- In case any breaks are necessary, the breaks should be about {this.durationBreaks} minutes long.
""";
}
private string PromptArriveAndDepart()
{
if (this.needToArriveAndDepart)
return "- The participants should have time to arrive and depart.";
return "- The participants do not need to arrive and depart.";
}
private string PromptSocialActivity()
{
if (this.doingSocialActivity)
return "- The participants will engage in a social activity after the meeting or seminar. This can be a team-building exercise, a sightseeing tour, or a visit to a local attraction. Please make a recommendation for an activity at '{this.inputLocation}'.";
return "- The participants will not engage in a social activity after the meeting or seminar.";
}
private string SystemPromptSocialActivity()
{
if(this.doingSocialActivity)
return """
- 16:00 Departure to the social event [Consider the time needed to get to the location].
- 17:00 [Insert your advice for the social activity here].
""";
return string.Empty;
}
private string SystemPromptDinner()
{
if(this.goingToDinner)
return """
- 19:00 Departure to the restaurant [Consider the time needed to get to the restaurant].
- 20:00 [Insert your advice for the dinner here.].
""";
return string.Empty;
}
private string PromptJoiningDinner()
{
if (this.goingToDinner)
return $"- The participants will join for a dinner after the meeting or seminar. Please make a recommendation for a restaurant at '{this.inputLocation}'.";
return "- The participants will not join for a dinner after the meeting or seminar.";
}
private string PromptVirtualMeeting()
{
if (this.isMeetingVirtual)
return "- The meeting or seminar will be held virtually, e.g, a call or webinar. The participants will join online.";
return "- The meeting or seminar will be held in person.";
}
private string PromptActiveParticipation()
{
if (this.activeParticipation)
return "- The participants should actively participate in the meeting or seminar. This can be done through discussions, questions, and contributions to the content.";
return "- The participants do not need to actively participate.";
}
private string PromptGettingToKnowEachOther()
{
if (this.introduceParticipants)
return "- The participants should introduce themselves to get to know each other. This can be done in a round or in pairs. The moderator should ensure that everyone has the opportunity to speak.";
return "- The participants do not need to introduce themselves.";
}
private string PromptFoci()
{
if (this.selectedFoci.Any())
{
return $"""
Out of this content, the following points should be focused on:
{this.ConvertItemsToMarkdown(this.selectedFoci)}
""";
}
return string.Empty;
}
private string PromptBriefly()
{
if (this.justBriefly.Any())
{
return $"""
The following points should be just briefly mentioned:
{this.ConvertItemsToMarkdown(this.justBriefly)}
""";
}
return string.Empty;
}
private string PromptWhoIsPresenting()
{
if (!string.IsNullOrWhiteSpace(this.inputWhoIsPresenting))
{
return $"""
The following persons will present the content:
{this.inputWhoIsPresenting}
""";
}
return string.Empty;
}
private string ConvertItemsToMarkdown(IEnumerable<string> items)
{
var markdown = new StringBuilder();
foreach (var item in items)
markdown.AppendLine($"- {item}");
return markdown.ToString();
}
}
@@ -1,18 +0,0 @@
namespace AIStudio.Components.Pages.Agenda;
public enum NumberParticipants
{
NOT_SPECIFIED,
PEER_TO_PEER, // 2 participants
SMALL_GROUP, // 3 - 5 participants
LARGE_GROUP, // 6 - 12 participants
MULTIPLE_SMALL_GROUPS, // 13 - 20 participants
MULTIPLE_LARGE_GROUPS, // 21 - 30 participants
SYMPOSIUM, // 31 - 100 participants
CONFERENCE, // 101 - 200 participants
CONGRESS, // 201 - 1000 participants
LARGE_EVENT, // 1000+ participants
}
@@ -1,24 +0,0 @@
namespace AIStudio.Components.Pages.Agenda;
public static class NumberParticipantsExtensions
{
public static string Name(this NumberParticipants numberParticipants) => numberParticipants switch
{
NumberParticipants.NOT_SPECIFIED => "Please select how many participants are expected",
NumberParticipants.PEER_TO_PEER => "2 (peer to peer)",
NumberParticipants.SMALL_GROUP => "3 - 5 (small group)",
NumberParticipants.LARGE_GROUP => "6 - 12 (large group)",
NumberParticipants.MULTIPLE_SMALL_GROUPS => "13 - 20 (multiple small groups)",
NumberParticipants.MULTIPLE_LARGE_GROUPS => "21 - 30 (multiple large groups)",
NumberParticipants.SYMPOSIUM => "31 - 100 (symposium)",
NumberParticipants.CONFERENCE => "101 - 200 (conference)",
NumberParticipants.CONGRESS => "201 - 1,000 (congress)",
NumberParticipants.LARGE_EVENT => "1,000+ (large event)",
_ => "Unknown"
};
}
@@ -1,34 +0,0 @@
@attribute [Route(Path.ASSISTANTS)]
<MudText Typo="Typo.h3" Class="mb-2 mr-3">
Assistants
</MudText>
<InnerScrolling HeaderHeight="6em">
<MudText Typo="Typo.h4" Class="mb-2 mr-3">
General
</MudText>
<MudStack Row="@true" Wrap="@Wrap.Wrap" Class="mb-3">
<AssistantBlock Name="Text Summarizer" Description="Using a LLM to summarize a given text." Icon="@Icons.Material.Filled.TextSnippet" Link="@Path.ASSISTANT_SUMMARIZER"/>
<AssistantBlock Name="Translation" Description="Translate text into another language." Icon="@Icons.Material.Filled.Translate" Link="@Path.ASSISTANT_TRANSLATION"/>
<AssistantBlock Name="Grammar & Spelling" Description="Check grammar and spelling of a given text." Icon="@Icons.Material.Filled.Edit" Link="@Path.ASSISTANT_GRAMMAR_SPELLING"/>
<AssistantBlock Name="Rewrite & Improve" Description="Rewrite and improve a given text for a chosen style." Icon="@Icons.Material.Filled.Edit" Link="@Path.ASSISTANT_REWRITE"/>
</MudStack>
<MudText Typo="Typo.h4" Class="mb-2 mr-3 mt-6">
Business
</MudText>
<MudStack Row="@true" Wrap="@Wrap.Wrap" Class="mb-3">
<AssistantBlock Name="Agenda Planner" Description="Generate an agenda for a given meeting, seminar, etc." Icon="@Icons.Material.Filled.CalendarToday" Link="@Path.ASSISTANT_AGENDA"/>
<AssistantBlock Name="Icon Finder" Description="Using a LLM to find an icon for a given context." Icon="@Icons.Material.Filled.FindInPage" Link="@Path.ASSISTANT_ICON_FINDER"/>
</MudStack>
<MudText Typo="Typo.h4" Class="mb-2 mr-3 mt-6">
Software Engineering
</MudText>
<MudStack Row="@true" Wrap="@Wrap.Wrap" Class="mb-3">
<AssistantBlock Name="Coding" Description="Get coding and debugging support from a LLM." Icon="@Icons.Material.Filled.Code" Link="@Path.ASSISTANT_CODING"/>
</MudStack>
</InnerScrolling>
@@ -1,5 +0,0 @@
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components.Pages;
public partial class Assistants : ComponentBase;
@@ -1,93 +0,0 @@
@attribute [Route(Path.CHAT)]
@using AIStudio.Chat
@using AIStudio.Settings.DataModel
@inherits MSGComponentBase
<MudText Typo="Typo.h3" Class="mb-2 mr-3">
@if (this.chatThread is not null && this.chatThread.WorkspaceId != Guid.Empty)
{
@($"Chat in Workspace \"{this.currentWorkspaceName}\"")
}
else
{
@("Temporary Chat")
}
</MudText>
<ProviderSelection @bind-ProviderSettings="@this.providerSettings"/>
<InnerScrolling HeaderHeight="12.3em">
<ChildContent>
@if (this.chatThread is not null)
{
foreach (var block in this.chatThread.Blocks.OrderBy(n => n.Time))
{
<ContentBlockComponent Role="@block.Role" Type="@block.ContentType" Time="@block.Time" Content="@block.Content"/>
}
}
</ChildContent>
<FooterContent>
<MudPaper Style="flex: 0 0 auto;">
<MudTextField T="string" @ref="@this.inputField" @bind-Text="@this.userInput" Variant="Variant.Outlined" AutoGrow="@true" Lines="3" MaxLines="12" Label="@this.InputLabel" Placeholder="@this.ProviderPlaceholder" Adornment="Adornment.End" AdornmentIcon="@Icons.Material.Filled.Send" OnAdornmentClick="() => this.SendMessage()" ReadOnly="!this.IsProviderSelected || this.isStreaming" Immediate="@true" OnKeyUp="this.InputKeyEvent" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
</MudPaper>
<MudPaper Class="mt-1" Outlined="@true">
<MudToolBar WrapContent="true">
@if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is not WorkspaceStorageBehavior.DISABLE_WORKSPACES)
{
<MudTooltip Text="Your workspaces" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
<MudIconButton Icon="@Icons.Material.Filled.SnippetFolder" OnClick="() => this.ToggleWorkspaces()"/>
</MudTooltip>
}
@if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_MANUALLY)
{
<MudTooltip Text="Save chat" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
<MudIconButton Icon="@Icons.Material.Filled.Save" OnClick="() => this.SaveThread()" Disabled="@(!this.CanThreadBeSaved)"/>
</MudTooltip>
}
<MudTooltip Text="Start temporary chat" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
<MudIconButton Icon="@Icons.Material.Filled.AddComment" OnClick="() => this.StartNewChat(useSameWorkspace: false)"/>
</MudTooltip>
@if (!string.IsNullOrWhiteSpace(this.currentWorkspaceName))
{
<MudTooltip Text="@this.TooltipAddChatToWorkspace" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
<MudIconButton Icon="@Icons.Material.Filled.CommentBank" OnClick="() => this.StartNewChat(useSameWorkspace: true)"/>
</MudTooltip>
}
@if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY)
{
<MudTooltip Text="Delete this chat & start a new one" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
<MudIconButton Icon="@Icons.Material.Filled.Refresh" OnClick="() => this.StartNewChat(useSameWorkspace: true, deletePreviousChat: true)" Disabled="@(!this.CanThreadBeSaved)"/>
</MudTooltip>
}
@if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is not WorkspaceStorageBehavior.DISABLE_WORKSPACES)
{
<MudTooltip Text="Move the chat to a workspace, or to another if it is already in one." Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
<MudIconButton Icon="@Icons.Material.Filled.MoveToInbox" Disabled="@(!this.CanThreadBeSaved)" OnClick="() => this.MoveChatToWorkspace()"/>
</MudTooltip>
}
</MudToolBar>
</MudPaper>
</FooterContent>
</InnerScrolling>
@if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior != WorkspaceStorageBehavior.DISABLE_WORKSPACES)
{
<MudDrawer @bind-Open="@this.workspacesVisible" Width="40em" Height="100%" Anchor="Anchor.Start" Variant="DrawerVariant.Temporary" Elevation="1">
<MudDrawerHeader>
<MudStack Row="@true" AlignItems="AlignItems.Center">
<MudText Typo="Typo.h6" Class="mr-3">
Your workspaces
</MudText>
<MudIconButton Icon="@Icons.Material.Filled.Close" Variant="Variant.Filled" Color="Color.Default" Size="Size.Small" OnClick="() => this.ToggleWorkspaces()"/>
</MudStack>
</MudDrawerHeader>
<MudDrawerContainer Class="ml-6">
<Workspaces @ref="this.workspaces" @bind-CurrentChatThread="@this.chatThread" LoadedChatWasChanged="this.LoadedChatChanged"/>
</MudDrawerContainer>
</MudDrawer>
}
@@ -1,429 +0,0 @@
using AIStudio.Chat;
using AIStudio.Components.Blocks;
using AIStudio.Components.CommonDialogs;
using AIStudio.Provider;
using AIStudio.Settings;
using AIStudio.Settings.DataModel;
using AIStudio.Tools;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using DialogOptions = AIStudio.Components.CommonDialogs.DialogOptions;
namespace AIStudio.Components.Pages;
/// <summary>
/// The chat page.
/// </summary>
public partial class Chat : MSGComponentBase, IAsyncDisposable
{
[Inject]
private SettingsManager SettingsManager { get; set; } = null!;
[Inject]
public IJSRuntime JsRuntime { get; init; } = null!;
[Inject]
private ThreadSafeRandom RNG { get; init; } = null!;
[Inject]
public IDialogService DialogService { get; set; } = null!;
private const Placement TOOLBAR_TOOLTIP_PLACEMENT = Placement.Bottom;
private static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new();
private AIStudio.Settings.Provider providerSettings;
private ChatThread? chatThread;
private bool hasUnsavedChanges;
private bool isStreaming;
private string userInput = string.Empty;
private string currentWorkspaceName = string.Empty;
private Guid currentWorkspaceId = Guid.Empty;
private bool workspacesVisible;
private Workspaces? workspaces;
// Unfortunately, we need the input field reference to clear it after sending a message.
// This is necessary because we have to handle the key events ourselves. Otherwise,
// the clearing would be done automatically.
private MudTextField<string> inputField = null!;
#region Overrides of ComponentBase
protected override async Task OnInitializedAsync()
{
this.ApplyFilters([], [ Event.HAS_CHAT_UNSAVED_CHANGES, Event.RESET_CHAT_STATE ]);
// Configure the spellchecking for the user input:
this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES);
if (this.SettingsManager.ConfigurationData.Chat.PreselectOptions)
{
this.providerSettings = this.SettingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == this.SettingsManager.ConfigurationData.Chat.PreselectedProvider);
}
var deferredContent = MessageBus.INSTANCE.CheckDeferredMessages<ChatThread>(Event.SEND_TO_CHAT).FirstOrDefault();
if (deferredContent is not null)
{
this.chatThread = deferredContent;
if (this.chatThread is not null)
{
var firstUserBlock = this.chatThread.Blocks.FirstOrDefault(x => x.Role == ChatRole.USER);
if (firstUserBlock is not null)
{
this.chatThread.Name = firstUserBlock.Content switch
{
ContentText textBlock => this.ExtractThreadName(textBlock.Text),
_ => "Thread"
};
}
}
}
await base.OnInitializedAsync();
}
#endregion
private bool IsProviderSelected => this.providerSettings.UsedProvider != Providers.NONE;
private string ProviderPlaceholder => this.IsProviderSelected ? "Type your input here..." : "Select a provider first";
private string InputLabel => this.IsProviderSelected ? $"Your Prompt (use selected instance '{this.providerSettings.InstanceName}', provider '{this.providerSettings.UsedProvider.ToName()}')" : "Select a provider first";
private bool CanThreadBeSaved => this.chatThread is not null && this.chatThread.Blocks.Count > 0;
private string TooltipAddChatToWorkspace => $"Start new chat in workspace \"{this.currentWorkspaceName}\"";
private async Task SendMessage()
{
if (!this.IsProviderSelected)
return;
// Create a new chat thread if necessary:
var threadName = this.ExtractThreadName(this.userInput);
if (this.chatThread is null)
{
this.chatThread = new()
{
WorkspaceId = this.currentWorkspaceId,
ChatId = Guid.NewGuid(),
Name = threadName,
Seed = this.RNG.Next(),
SystemPrompt = SystemPrompts.DEFAULT,
Blocks = [],
};
}
else
{
// Set the thread name if it is empty:
if (string.IsNullOrWhiteSpace(this.chatThread.Name))
this.chatThread.Name = threadName;
}
//
// Add the user message to the thread:
//
var time = DateTimeOffset.Now;
this.chatThread?.Blocks.Add(new ContentBlock
{
Time = time,
ContentType = ContentType.TEXT,
Role = ChatRole.USER,
Content = new ContentText
{
Text = this.userInput,
},
});
// Save the chat:
if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY)
{
await this.SaveThread();
this.hasUnsavedChanges = false;
this.StateHasChanged();
}
//
// Add the AI response to the thread:
//
var aiText = new ContentText
{
// We have to wait for the remote
// for the content stream:
InitialRemoteWait = true,
};
this.chatThread?.Blocks.Add(new ContentBlock
{
Time = time,
ContentType = ContentType.TEXT,
Role = ChatRole.AI,
Content = aiText,
});
// Clear the input field:
await this.inputField.Clear();
this.userInput = string.Empty;
// Enable the stream state for the chat component:
this.isStreaming = true;
this.hasUnsavedChanges = true;
this.StateHasChanged();
// 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.JsRuntime, this.SettingsManager, this.providerSettings.Model, this.chatThread);
// Save the chat:
if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY)
{
await this.SaveThread();
this.hasUnsavedChanges = false;
}
// Disable the stream state:
this.isStreaming = false;
this.StateHasChanged();
}
private async Task InputKeyEvent(KeyboardEventArgs keyEvent)
{
this.hasUnsavedChanges = true;
var key = keyEvent.Code.ToLowerInvariant();
// Was the enter key (either enter or numpad enter) pressed?
var isEnter = key is "enter" or "numpadenter";
// Was a modifier key pressed as well?
var isModifier = keyEvent.AltKey || keyEvent.CtrlKey || keyEvent.MetaKey || keyEvent.ShiftKey;
// Depending on the user's settings, might react to shortcuts:
switch (this.SettingsManager.ConfigurationData.Chat.ShortcutSendBehavior)
{
case SendBehavior.ENTER_IS_SENDING:
if (!isModifier && isEnter)
await this.SendMessage();
break;
case SendBehavior.MODIFER_ENTER_IS_SENDING:
if (isEnter && isModifier)
await this.SendMessage();
break;
}
}
private void ToggleWorkspaces()
{
this.workspacesVisible = !this.workspacesVisible;
}
private async Task SaveThread()
{
if(this.workspaces is null)
return;
if(this.chatThread is null)
return;
if (!this.CanThreadBeSaved)
return;
await this.workspaces.StoreChat(this.chatThread);
this.hasUnsavedChanges = false;
}
private string ExtractThreadName(string firstUserInput)
{
// We select the first 10 words of the user input:
var words = firstUserInput.Split(' ', StringSplitOptions.RemoveEmptyEntries);
var threadName = string.Join(' ', words.Take(10));
// If the thread name is empty, we use a default name:
if (string.IsNullOrWhiteSpace(threadName))
threadName = "Thread";
return threadName;
}
private async Task StartNewChat(bool useSameWorkspace = false, bool deletePreviousChat = false)
{
if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_MANUALLY && this.hasUnsavedChanges)
{
var dialogParameters = new DialogParameters
{
{ "Message", "Are you sure you want to start a new chat? All unsaved changes will be lost." },
};
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Delete Chat", dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result;
if (dialogResult is null || dialogResult.Canceled)
return;
}
if (this.chatThread is not null && this.workspaces is not null && deletePreviousChat)
{
string chatPath;
if (this.chatThread.WorkspaceId == Guid.Empty)
{
chatPath = System.IO.Path.Join(SettingsManager.DataDirectory, "tempChats", this.chatThread.ChatId.ToString());
}
else
{
chatPath = System.IO.Path.Join(SettingsManager.DataDirectory, "workspaces", this.chatThread.WorkspaceId.ToString(), this.chatThread.ChatId.ToString());
}
await this.workspaces.DeleteChat(chatPath, askForConfirmation: false, unloadChat: true);
}
this.isStreaming = false;
this.hasUnsavedChanges = false;
this.userInput = string.Empty;
if (!useSameWorkspace)
{
this.chatThread = null;
this.currentWorkspaceId = Guid.Empty;
this.currentWorkspaceName = string.Empty;
}
else
{
this.chatThread = new()
{
WorkspaceId = this.currentWorkspaceId,
ChatId = Guid.NewGuid(),
Name = string.Empty,
Seed = this.RNG.Next(),
SystemPrompt = "You are a helpful assistant!",
Blocks = [],
};
}
await this.inputField.Clear();
}
private async Task MoveChatToWorkspace()
{
if(this.chatThread is null)
return;
if(this.workspaces is null)
return;
if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_MANUALLY && this.hasUnsavedChanges)
{
var confirmationDialogParameters = new DialogParameters
{
{ "Message", "Are you sure you want to move this chat? All unsaved changes will be lost." },
};
var confirmationDialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Unsaved Changes", confirmationDialogParameters, DialogOptions.FULLSCREEN);
var confirmationDialogResult = await confirmationDialogReference.Result;
if (confirmationDialogResult is null || confirmationDialogResult.Canceled)
return;
}
var dialogParameters = new DialogParameters
{
{ "Message", "Please select the workspace where you want to move the chat to." },
{ "SelectedWorkspace", this.chatThread?.WorkspaceId },
{ "ConfirmText", "Move chat" },
};
var dialogReference = await this.DialogService.ShowAsync<WorkspaceSelectionDialog>("Move Chat to Workspace", dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result;
if (dialogResult is null || dialogResult.Canceled)
return;
var workspaceId = dialogResult.Data is Guid id ? id : default;
if (workspaceId == Guid.Empty)
return;
// Delete the chat from the current workspace or the temporary storage:
if (this.chatThread!.WorkspaceId == Guid.Empty)
{
// Case: The chat is stored in the temporary storage:
await this.workspaces.DeleteChat(System.IO.Path.Join(SettingsManager.DataDirectory, "tempChats", this.chatThread.ChatId.ToString()), askForConfirmation: false, unloadChat: false);
}
else
{
// Case: The chat is stored in a workspace.
await this.workspaces.DeleteChat(System.IO.Path.Join(SettingsManager.DataDirectory, "workspaces", this.chatThread.WorkspaceId.ToString(), this.chatThread.ChatId.ToString()), askForConfirmation: false, unloadChat: false);
}
this.chatThread!.WorkspaceId = workspaceId;
await this.SaveThread();
this.currentWorkspaceId = this.chatThread.WorkspaceId;
this.currentWorkspaceName = await this.workspaces.LoadWorkspaceName(this.chatThread.WorkspaceId);
}
private async Task LoadedChatChanged()
{
if(this.workspaces is null)
return;
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 this.workspaces.LoadWorkspaceName(this.chatThread.WorkspaceId);
await this.inputField.Clear();
}
private void ResetState()
{
this.isStreaming = false;
this.hasUnsavedChanges = false;
this.userInput = string.Empty;
this.currentWorkspaceId = Guid.Empty;
this.currentWorkspaceName = string.Empty;
this.chatThread = null;
}
#region Overrides of MSGComponentBase
public override Task ProcessMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
{
switch (triggeredEvent)
{
case Event.RESET_CHAT_STATE:
this.ResetState();
break;
}
return Task.CompletedTask;
}
public override Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) where TResult : default where TPayload : default
{
switch (triggeredEvent)
{
case Event.HAS_CHAT_UNSAVED_CHANGES:
if(this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY)
return Task.FromResult((TResult?) (object) false);
return Task.FromResult((TResult?)(object)this.hasUnsavedChanges);
}
return Task.FromResult(default(TResult));
}
#endregion
#region Implementation of IAsyncDisposable
public async ValueTask DisposeAsync()
{
if(this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY)
{
await this.SaveThread();
this.hasUnsavedChanges = false;
}
}
#endregion
}
@@ -1,30 +0,0 @@
@attribute [Route(Path.ASSISTANT_CODING)]
@inherits AssistantBaseCore
<MudExpansionPanels Class="mb-3">
@for (var contextIndex = 0; contextIndex < this.codingContexts.Count; contextIndex++)
{
var codingContext = this.codingContexts[contextIndex];
<ExpansionPanel HeaderText="@codingContext.Id" HeaderIcon="@Icons.Material.Filled.Code">
<CodingContextItem @bind-CodingContext="@codingContext"/>
</ExpansionPanel>
}
</MudExpansionPanels>
<MudButton Variant="Variant.Filled" OnClick="() => this.AddCodingContext()" Class="mb-3">
Add context
</MudButton>
<MudStack Row="@false" Class="mb-3">
<MudTextSwitch Label="Do you want to provide compiler messages?" @bind-Value="@this.provideCompilerMessages" LabelOn="Yes, provide compiler messages" LabelOff="No, there are no compiler messages" />
@if (this.provideCompilerMessages)
{
<MudTextField T="string" @bind-Text="@this.compilerMessages" Validation="@this.ValidatingCompilerMessages" AdornmentIcon="@Icons.Material.Filled.Error" Adornment="Adornment.Start" Label="Compiler messages" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
}
</MudStack>
<MudTextField T="string" @bind-Text="@this.questions" Validation="@this.ValidateQuestions" AdornmentIcon="@Icons.Material.Filled.QuestionMark" Adornment="Adornment.Start" Label="Your question(s)" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
<MudButton Variant="Variant.Filled" Color="Color.Info" OnClick="() => this.GetSupport()" Class="mb-3">
Get support
</MudButton>
@@ -1,159 +0,0 @@
using System.Text;
using AIStudio.Tools;
namespace AIStudio.Components.Pages.Coding;
public partial class AssistantCoding : AssistantBaseCore
{
protected override string Title => "Coding Assistant";
protected override string Description =>
"""
This coding assistant supports you in writing code. Provide some coding context by copying and pasting
your code into the input fields. You might assign an ID to your code snippet to easily reference it later.
When you have compiler messages, you can paste them into the input fields to get help with debugging as well.
""";
protected override string SystemPrompt =>
"""
You are a friendly, helpful senior software developer with extensive experience in various programming languages
and concepts. You are familiar with principles like DRY, KISS, YAGNI, and SOLID and can apply and explain them.
You know object-oriented programming, as well as functional programming and procedural programming. You are also
familiar with design patterns and can explain them. You are an expert of debugging and can help with compiler
messages. You can also help with code refactoring and optimization.
When the user asks in a different language than English, you answer in the same language!
""";
protected override IReadOnlyList<IButtonData> FooterButtons =>
[
new SendToButton
{
Self = SendTo.CODING_ASSISTANT,
},
];
protected override void ResetFrom()
{
this.codingContexts.Clear();
this.compilerMessages = string.Empty;
this.questions = string.Empty;
if (!this.MightPreselectValues())
{
this.provideCompilerMessages = false;
}
}
protected override bool MightPreselectValues()
{
if (this.SettingsManager.ConfigurationData.Coding.PreselectOptions)
{
this.provideCompilerMessages = this.SettingsManager.ConfigurationData.Coding.PreselectCompilerMessages;
this.providerSettings = this.SettingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == this.SettingsManager.ConfigurationData.Coding.PreselectedProvider);
return true;
}
return false;
}
private readonly List<CodingContext> codingContexts = new();
private bool provideCompilerMessages;
private string compilerMessages = string.Empty;
private string questions = string.Empty;
#region Overrides of ComponentBase
protected override async Task OnInitializedAsync()
{
this.MightPreselectValues();
var deferredContent = MessageBus.INSTANCE.CheckDeferredMessages<string>(Event.SEND_TO_CODING_ASSISTANT).FirstOrDefault();
if (deferredContent is not null)
this.questions = deferredContent;
await base.OnInitializedAsync();
}
#endregion
private string? ValidatingCompilerMessages(string checkCompilerMessages)
{
if(!this.provideCompilerMessages)
return null;
if(string.IsNullOrWhiteSpace(checkCompilerMessages))
return "Please provide the compiler messages.";
return null;
}
private string? ValidateQuestions(string checkQuestions)
{
if(string.IsNullOrWhiteSpace(checkQuestions))
return "Please provide your questions.";
return null;
}
private void AddCodingContext()
{
this.codingContexts.Add(new()
{
Id = $"Context {this.codingContexts.Count + 1}",
Language = this.SettingsManager.ConfigurationData.Coding.PreselectOptions ? this.SettingsManager.ConfigurationData.Coding.PreselectedProgrammingLanguage : default,
OtherLanguage = this.SettingsManager.ConfigurationData.Coding.PreselectOptions ? this.SettingsManager.ConfigurationData.Coding.PreselectedOtherProgrammingLanguage : string.Empty,
});
}
private async Task GetSupport()
{
await this.form!.Validate();
if (!this.inputIsValid)
return;
var sbContext = new StringBuilder();
if (this.codingContexts.Count > 0)
{
sbContext.AppendLine("I have the following coding context:");
sbContext.AppendLine();
foreach (var codingContext in this.codingContexts)
{
sbContext.AppendLine($"ID: {codingContext.Id}");
if(codingContext.Language is not CommonCodingLanguages.OTHER)
sbContext.AppendLine($"Language: {codingContext.Language.Name()}");
else
sbContext.AppendLine($"Language: {codingContext.OtherLanguage}");
sbContext.AppendLine("Content:");
sbContext.AppendLine("```");
sbContext.AppendLine(codingContext.Code);
sbContext.AppendLine("```");
sbContext.AppendLine();
}
}
var sbCompilerMessages = new StringBuilder();
if (this.provideCompilerMessages)
{
sbCompilerMessages.AppendLine("I have the following compiler messages:");
sbCompilerMessages.AppendLine();
sbCompilerMessages.AppendLine("```");
sbCompilerMessages.AppendLine(this.compilerMessages);
sbCompilerMessages.AppendLine("```");
sbCompilerMessages.AppendLine();
}
this.CreateChatThread();
var time = this.AddUserRequest(
$"""
{sbContext}
{sbCompilerMessages}
My questions are:
{this.questions}
""");
await this.AddAIResponseAsync(time);
}
}
@@ -1,16 +0,0 @@
namespace AIStudio.Components.Pages.Coding;
public sealed class CodingContext(string id, CommonCodingLanguages language, string otherLanguage, string code)
{
public CodingContext() : this(string.Empty, CommonCodingLanguages.NONE, string.Empty, string.Empty)
{
}
public string Id { get; set; } = id;
public CommonCodingLanguages Language { get; set; } = language;
public string OtherLanguage { get; set; } = otherLanguage;
public string Code { get; set; } = code;
}
@@ -1,14 +0,0 @@
<MudTextField T="string" @bind-Text="@this.CodingContext.Id" AdornmentIcon="@Icons.Material.Filled.Numbers" Adornment="Adornment.Start" Label="(Optional) Identifier" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3"/>
<MudStack Row="@true" AlignItems="AlignItems.Center" Class="mb-3">
<MudSelect T="CommonCodingLanguages" @bind-Value="@this.CodingContext.Language" AdornmentIcon="@Icons.Material.Filled.Code" Adornment="Adornment.Start" Label="Language" Variant="Variant.Outlined" Margin="Margin.Dense">
@foreach (var language in Enum.GetValues<CommonCodingLanguages>())
{
<MudSelectItem Value="@language">@language.Name()</MudSelectItem>
}
</MudSelect>
@if (this.CodingContext.Language is CommonCodingLanguages.OTHER)
{
<MudTextField T="string" @bind-Text="@this.CodingContext.OtherLanguage" Validation="@this.ValidatingOtherLanguage" Label="Other language" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
}
</MudStack>
<MudTextField T="string" @bind-Text="@this.CodingContext.Code" Validation="@this.ValidatingCode" AdornmentIcon="@Icons.Material.Filled.DocumentScanner" Adornment="Adornment.Start" Label="Your code" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES" />
@@ -1,50 +0,0 @@
using AIStudio.Settings;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components.Pages.Coding;
public partial class CodingContextItem : ComponentBase
{
[Parameter]
public CodingContext CodingContext { get; set; } = new();
[Parameter]
public EventCallback<CodingContext> CodingContextChanged { get; set; }
[Inject]
protected SettingsManager SettingsManager { get; set; } = null!;
private static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new();
#region Overrides of ComponentBase
protected override async Task OnParametersSetAsync()
{
// Configure the spellchecking for the user input:
this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES);
await base.OnParametersSetAsync();
}
#endregion
private string? ValidatingCode(string code)
{
if(string.IsNullOrWhiteSpace(code))
return $"{this.CodingContext.Id}: Please provide your input.";
return null;
}
private string? ValidatingOtherLanguage(string language)
{
if(this.CodingContext.Language != CommonCodingLanguages.OTHER)
return null;
if(string.IsNullOrWhiteSpace(language))
return "Please specify the language.";
return null;
}
}
@@ -1,43 +0,0 @@
namespace AIStudio.Components.Pages.Coding;
public static class CommonCodingLanguageExtensions
{
public static string Name(this CommonCodingLanguages language) => language switch
{
CommonCodingLanguages.NONE => "None",
CommonCodingLanguages.BASH => "Bash",
CommonCodingLanguages.BLAZOR => ".NET Blazor",
CommonCodingLanguages.C => "C",
CommonCodingLanguages.CPP => "C++",
CommonCodingLanguages.CSHARP => "C#",
CommonCodingLanguages.CSS => "CSS",
CommonCodingLanguages.FORTRAN => "Fortran",
CommonCodingLanguages.GDSCRIPT => "GDScript",
CommonCodingLanguages.GO => "Go",
CommonCodingLanguages.HTML => "HTML",
CommonCodingLanguages.JAVA => "Java",
CommonCodingLanguages.JAVASCRIPT => "JavaScript",
CommonCodingLanguages.JSON => "JSON",
CommonCodingLanguages.JULIA => "Julia",
CommonCodingLanguages.KOTLIN => "Kotlin",
CommonCodingLanguages.LUA => "Lua",
CommonCodingLanguages.MARKDOWN => "Markdown",
CommonCodingLanguages.MATHEMATICA => "Mathematica",
CommonCodingLanguages.MATLAB => "MATLAB",
CommonCodingLanguages.PHP => "PHP",
CommonCodingLanguages.POWERSHELL => "PowerShell",
CommonCodingLanguages.PROLOG => "Prolog",
CommonCodingLanguages.PYTHON => "Python",
CommonCodingLanguages.R => "R",
CommonCodingLanguages.RUBY => "Ruby",
CommonCodingLanguages.RUST => "Rust",
CommonCodingLanguages.SQL => "SQL",
CommonCodingLanguages.SWIFT => "Swift",
CommonCodingLanguages.TYPESCRIPT => "TypeScript",
CommonCodingLanguages.XML => "XML",
CommonCodingLanguages.OTHER => "Other",
_ => "Unknown"
};
}
@@ -1,39 +0,0 @@
namespace AIStudio.Components.Pages.Coding;
public enum CommonCodingLanguages
{
NONE,
BASH,
BLAZOR,
C,
CPP,
CSHARP,
CSS,
FORTRAN,
GDSCRIPT,
GO,
HTML,
JAVA,
JAVASCRIPT,
JSON,
JULIA,
KOTLIN,
LUA,
MARKDOWN,
MATHEMATICA,
MATLAB,
PHP,
POWERSHELL,
PROLOG,
PYTHON,
R,
RUBY,
RUST,
SQL,
SWIFT,
TYPESCRIPT,
XML,
OTHER,
}
@@ -1,11 +0,0 @@
@using AIStudio.Tools
@attribute [Route(Path.ASSISTANT_GRAMMAR_SPELLING)]
@inherits AssistantBaseCore
<MudTextField T="string" @bind-Text="@this.inputText" Validation="@this.ValidateText" AdornmentIcon="@Icons.Material.Filled.DocumentScanner" Adornment="Adornment.Start" Label="Your input to check" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
<EnumSelection T="CommonLanguages" NameFunc="@(language => language.NameSelectingOptional())" @bind-Value="@this.selectedTargetLanguage" Icon="@Icons.Material.Filled.Translate" Label="Language" AllowOther="@true" OtherValue="CommonLanguages.OTHER" @bind-OtherInput="@this.customTargetLanguage" ValidateOther="@this.ValidateCustomLanguage" LabelOther="Custom language" />
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
<MudButton Variant="Variant.Filled" Class="mb-3" OnClick="() => this.ProofreadText()">
Proofread
</MudButton>
@@ -1,132 +0,0 @@
using AIStudio.Chat;
using AIStudio.Tools;
namespace AIStudio.Components.Pages.GrammarSpelling;
public partial class AssistantGrammarSpelling : AssistantBaseCore
{
protected override string Title => "Grammar & Spelling Checker";
protected override string Description =>
"""
Check the grammar and spelling of a text.
""";
protected override string SystemPrompt =>
$"""
You are an expert in languages and their rules. For example, you know how US and UK English or German in
Germany and German in Austria differ. You receive text as input. You check the spelling and grammar of
this text according to the rules of {this.SystemPromptLanguage()}. You never add information. You
never ask the user for additional information. You do not attempt to improve the wording of the text.
Your response includes only the corrected text. Do not explain your changes. If no changes are needed,
you return the text unchanged.
""";
protected override bool ShowResult => false;
protected override bool ShowDedicatedProgress => true;
protected override IReadOnlyList<IButtonData> FooterButtons =>
[
new ButtonData("Copy result", Icons.Material.Filled.ContentCopy, Color.Default, string.Empty, () => this.CopyToClipboard(this.correctedText)),
new SendToButton
{
Self = SendTo.GRAMMAR_SPELLING_ASSISTANT,
UseResultingContentBlockData = false,
GetText = () => string.IsNullOrWhiteSpace(this.correctedText) ? this.inputText : this.correctedText
},
];
protected override ChatThread ConvertToChatThread => (this.chatThread ?? new()) with
{
SystemPrompt = SystemPrompts.DEFAULT,
};
protected override void ResetFrom()
{
this.inputText = string.Empty;
this.correctedText = string.Empty;
if (!this.MightPreselectValues())
{
this.selectedTargetLanguage = CommonLanguages.AS_IS;
this.customTargetLanguage = string.Empty;
}
}
protected override bool MightPreselectValues()
{
if (this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectOptions)
{
this.selectedTargetLanguage = this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectedTargetLanguage;
this.customTargetLanguage = this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectedOtherLanguage;
this.providerSettings = this.SettingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectedProvider);
return true;
}
return false;
}
#region Overrides of ComponentBase
protected override async Task OnInitializedAsync()
{
this.MightPreselectValues();
var deferredContent = MessageBus.INSTANCE.CheckDeferredMessages<string>(Event.SEND_TO_GRAMMAR_SPELLING_ASSISTANT).FirstOrDefault();
if (deferredContent is not null)
this.inputText = deferredContent;
await base.OnInitializedAsync();
}
#endregion
private string inputText = string.Empty;
private CommonLanguages selectedTargetLanguage;
private string customTargetLanguage = string.Empty;
private string correctedText = string.Empty;
private string? ValidateText(string text)
{
if(string.IsNullOrWhiteSpace(text))
return "Please provide a text as input. You might copy the desired text from a document or a website.";
return null;
}
private string? ValidateCustomLanguage(string language)
{
if(this.selectedTargetLanguage == CommonLanguages.OTHER && string.IsNullOrWhiteSpace(language))
return "Please provide a custom language.";
return null;
}
private string SystemPromptLanguage()
{
var lang = this.selectedTargetLanguage switch
{
CommonLanguages.AS_IS => "the source language",
CommonLanguages.OTHER => this.customTargetLanguage,
_ => $"{this.selectedTargetLanguage.Name()}",
};
if (string.IsNullOrWhiteSpace(lang))
return "the source language";
return lang;
}
private async Task ProofreadText()
{
await this.form!.Validate();
if (!this.inputIsValid)
return;
this.CreateChatThread();
var time = this.AddUserRequest(this.inputText);
this.correctedText = await this.AddAIResponseAsync(time);
await this.JsRuntime.GenerateAndShowDiff(this.inputText, this.correctedText);
}
}
@@ -1,41 +0,0 @@
@attribute [Route(Path.HOME)]
@using AIStudio.Tools
<MudImage Src="svg/banner.svg" />
<MudText Typo="Typo.h3" Class="mt-2 mb-2">Let's get started</MudText>
<InnerScrolling HeaderHeight="288px - 8em">
<MudExpansionPanels Class="mb-3" MultiExpansion="@false">
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.MenuBook" HeaderText="Introduction" IsExpanded="@true">
<MudText Typo="Typo.h5" Class="mb-3">
Welcome to MindWork AI Studio!
</MudText>
<MudText Typo="Typo.body1" Class="mb-3" Style="text-align: justify; hyphens: auto;">
Thank you for considering MindWork AI Studio for your AI needs. This app is designed to help you harness
the power of Large Language Models (LLMs). Please note that this app doesn't come with an integrated
LLM. Instead, you will need to bring an API key from a suitable provider.
</MudText>
<MudText Typo="Typo.body1" Class="mb-3">
Here's what makes MindWork AI Studio stand out:
</MudText>
<MudTextList Icon="@Icons.Material.Filled.CheckCircle" Clickable="@true" Items="@ITEMS_ADVANTAGES" Class="mb-3"/>
<MudText Typo="Typo.body1" Class="mb-3">
We hope you enjoy using MindWork AI Studio to bring your AI projects to life!
</MudText>
</ExpansionPanel>
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.EventNote" HeaderText="Last Changelog">
<MudMarkdown Value="@this.LastChangeContent" OverrideHeaderTypo="@Markdown.OverrideHeaderTypo"/>
</ExpansionPanel>
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Lightbulb" HeaderText="Vision">
<Vision/>
</ExpansionPanel>
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.RocketLaunch" HeaderText="Quick Start Guide">
<MudMarkdown OverrideHeaderTypo="@Markdown.OverrideHeaderTypo" Value="@QUICK_START_GUIDE"/>
</ExpansionPanel>
</MudExpansionPanels>
</InnerScrolling>
@@ -1,89 +0,0 @@
using AIStudio.Components.Blocks;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components.Pages;
public partial class Home : ComponentBase
{
[Inject]
private HttpClient HttpClient { get; set; } = null!;
private string LastChangeContent { get; set; } = string.Empty;
#region Overrides of ComponentBase
protected override async Task OnInitializedAsync()
{
await this.ReadLastChangeAsync();
await base.OnInitializedAsync();
}
#endregion
private async Task ReadLastChangeAsync()
{
var latest = Changelog.LOGS.MaxBy(n => n.Build);
var response = await this.HttpClient.GetAsync($"changelog/{latest.Filename}");
this.LastChangeContent = await response.Content.ReadAsStringAsync();
}
private static readonly TextItem[] ITEMS_ADVANTAGES =
[
new TextItem("Free of charge", "The app is free to use, both for personal and commercial purposes."),
new TextItem("Independence", "You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT4o etc.), Mistral, Anthropic (Claude), and self-hosted models using llama.cpp, ollama, LM Studio, or Fireworks. Support for Google Gemini and Replicate is planned."),
new TextItem("Unrestricted usage", "Unlike services like ChatGPT, which impose limits after intensive use, MindWork AI Studio offers unlimited usage through the providers API."),
new TextItem("Cost-effective", "You only pay for what you use, which can be cheaper than monthly subscription services like ChatGPT Plus, especially if used infrequently. But beware, here be dragons: For extremely intensive usage, the API costs can be significantly higher. Unfortunately, providers currently do not offer a way to display current costs in the app. Therefore, check your account with the respective provider to see how your costs are developing. When available, use prepaid and set a cost limit."),
new TextItem("Privacy", "The data entered into the app is not used for training by the providers since we are using the provider's API."),
new TextItem("Flexibility", "Choose the provider and model best suited for your current task."),
new TextItem("No bloatware", "The app requires minimal storage for installation and operates with low memory usage. Additionally, it has a minimal impact on system resources, which is beneficial for battery life."),
];
private const string QUICK_START_GUIDE =
"""
Ready to dive in and get started with MindWork AI Studio? This quick start guide will help you set up everything you need to start using the app.
## Step 1: Create an Account with OpenAI
1. Go to [OpenAI's platform](https://platform.openai.com/).
2. Click on "Sign up" and follow the instructions provided.
## Step 2: Obtain an API Key
1. After creating your OpenAI account, ensure you have a project named "default".
2. If you want, you can create a new project by clicking "Create project".
3. Navigate to a project of your choice, then click on "API keys" in the left-hand navigation menu.
4. You might need to validate your phone number. Click "Start verification" and follow the instructions.
5. Once verified, click "Create new secret key" to generate a new API key for the selected project.
6. Name your key something descriptive like "AI Studio Laptop."
7. Copy the displayed secret key. Remember, you will not be able to see this key again.
8. Store it in a password manager like [KeePassXC](https://keepassxc.org).
9. **Important:** Treat your API key like a secret password. Anyone with access to your key can use OpenAI's systems **at your expense**. **Do not share it with anyone!**
## Step 3: Add OpenAI as a Provider in MindWork AI Studio
1. Go to the settings section in MindWork AI Studio.
2. Click "Add Provider" and select OpenAI as the provider.
3. Paste your API key into the corresponding field.
## Step 4: Load OpenAI Models
1. Ensure you have an internet connection and your API key is valid.
2. Click "Reload" to retrieve a list of available OpenAI models.
3. Select "gpt-4o" to use the latest model.
4. Provide a name for this combination of provider, API key, and model. This is called the "instance name". For example, you can name it based on the usage context, such as "Personal OpenAI" or "Work OpenAI".
## Step 5: Save the Provider
1. Click "Add" to save the provider.
2. Depending on your operating system and settings, you may need to enter your login password. This is required to store and access your API key securely.
## Step 6: Add Funds to Your OpenAI Account
To utilize OpenAI's services, you need to add funds to your account. It's best to use the prepaid method to avoid high bills and set your budget. Navigate to the "Billing" section. Here, you can see your current balance. Click on "Add to credit balance" to deposit an amount. A balance of $10 is sufficient to start.
## Step 7: Start Chatting with the AI
1. Switch to the chat section in MindWork AI Studio.
2. Select your provider from the dropdown menu at the top.
3. Enter your question or message in the input field below to start your first chat with the AI.
## Additional Resources
There are also video tutorials on how to obtain your OpenAI API key. One example is this video by Anders Jensen: [https://www.youtube.com/watch?v=OB99E7Y1cMA](https://www.youtube.com/watch?v=OB99E7Y1cMA).
That's it! You're ready to explore and create with MindWork AI Studio. Enjoy your journey!
""";
}
@@ -1,22 +0,0 @@
@attribute [Route(Path.ASSISTANT_ICON_FINDER)]
@inherits AssistantBaseCore
<MudTextField T="string" @bind-Text="@this.inputContext" Validation="@this.ValidatingContext" AdornmentIcon="@Icons.Material.Filled.Description" Adornment="Adornment.Start" Label="Your context" Variant="Variant.Outlined" Lines="3" AutoGrow="@true" MaxLines="12" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
<MudStack Row="@true" AlignItems="AlignItems.Center" Class="mb-3">
<MudSelect T="IconSources" @bind-Value="@this.selectedIconSource" AdornmentIcon="@Icons.Material.Filled.Source" Adornment="Adornment.Start" Label="Your icon source" Variant="Variant.Outlined" Margin="Margin.Dense">
@foreach (var source in Enum.GetValues<IconSources>())
{
<MudSelectItem Value="@source">@source.Name()</MudSelectItem>
}
</MudSelect>
@if (this.selectedIconSource is not IconSources.GENERIC)
{
<MudButton Href="@this.selectedIconSource.URL()" Target="_blank" Variant="Variant.Filled" Size="Size.Medium">Open website</MudButton>
}
</MudStack>
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
<MudButton Variant="Variant.Filled" Class="mb-3" OnClick="() => this.FindIcon()">
Find icon
</MudButton>
@@ -1,100 +0,0 @@
using AIStudio.Tools;
namespace AIStudio.Components.Pages.IconFinder;
public partial class AssistantIconFinder : AssistantBaseCore
{
private string inputContext = string.Empty;
private IconSources selectedIconSource;
protected override string Title => "Icon Finder";
protected override string Description =>
"""
Finding the right icon for a context, such as for a piece of text, is not easy. The first challenge:
You need to extract a concept from your context, such as from a text. Let's take an example where
your text contains statements about multiple departments. The sought-after concept could be "departments."
The next challenge is that we need to anticipate the bias of the icon designers: under the search term
"departments," there may be no relevant icons or only unsuitable ones. Depending on the icon source,
it might be more effective to search for "buildings," for instance. LLMs assist you with both steps.
""";
protected override string SystemPrompt =>
"""
I can search for icons using US English keywords. Please help me come up with the right search queries.
I don't want you to translate my requests word-for-word into US English. Instead, you should provide keywords
that are likely to yield suitable icons. For example, I might ask for an icon about departments, but icons
related to the keyword "buildings" might be the best match. Provide your keywords in a Markdown list without
quotation marks.
""";
protected override IReadOnlyList<IButtonData> FooterButtons =>
[
new SendToButton
{
Self = SendTo.ICON_FINDER_ASSISTANT,
},
];
protected override void ResetFrom()
{
this.inputContext = string.Empty;
if (!this.MightPreselectValues())
{
this.selectedIconSource = IconSources.GENERIC;
}
}
protected override bool MightPreselectValues()
{
if (this.SettingsManager.ConfigurationData.IconFinder.PreselectOptions)
{
this.selectedIconSource = this.SettingsManager.ConfigurationData.IconFinder.PreselectedSource;
this.providerSettings = this.SettingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == this.SettingsManager.ConfigurationData.IconFinder.PreselectedProvider);
return true;
}
return false;
}
#region Overrides of ComponentBase
protected override async Task OnInitializedAsync()
{
this.MightPreselectValues();
var deferredContent = MessageBus.INSTANCE.CheckDeferredMessages<string>(Event.SEND_TO_ICON_FINDER_ASSISTANT).FirstOrDefault();
if (deferredContent is not null)
this.inputContext = deferredContent;
await base.OnInitializedAsync();
}
#endregion
private string? ValidatingContext(string context)
{
if(string.IsNullOrWhiteSpace(context))
return "Please provide a context. This will help the AI to find the right icon. You might type just a keyword or copy a sentence from your text, e.g., from a slide where you want to use the icon.";
return null;
}
private async Task FindIcon()
{
await this.form!.Validate();
if (!this.inputIsValid)
return;
this.CreateChatThread();
var time = this.AddUserRequest(
$"""
{this.selectedIconSource.Prompt()} I search for an icon for the following context:
```
{this.inputContext}
```
""");
await this.AddAIResponseAsync(time);
}
}
@@ -1,40 +0,0 @@
namespace AIStudio.Components.Pages.IconFinder;
public static class IconSourceExtensions
{
public static string Name(this IconSources iconSource) => iconSource switch
{
IconSources.FLAT_ICON => "Flaticon",
IconSources.FONT_AWESOME => "Font Awesome",
IconSources.MATERIAL_ICONS => "Material Icons",
IconSources.FEATHER_ICONS => "Feather Icons",
IconSources.BOOTSTRAP_ICONS => "Bootstrap Icons",
IconSources.ICONS8 => "Icons8",
_ => "Generic",
};
public static string Prompt(this IconSources iconSource) => iconSource switch
{
IconSources.FLAT_ICON => "My icon source is Flaticon.",
IconSources.FONT_AWESOME => "I look for an icon on Font Awesome. Please provide just valid icon names. Valid icon names are using the format `fa-icon-name`.",
IconSources.MATERIAL_ICONS => "I look for a Material icon. Please provide just valid icon names. Valid icon names are using the format `IconName`.",
IconSources.FEATHER_ICONS => "My icon source is Feather Icons. Please provide just valid icon names. Valid icon names usiing the format `icon-name`.",
IconSources.BOOTSTRAP_ICONS => "I look for an icon for Bootstrap. Please provide just valid icon names. Valid icon names are using the format `bi-icon-name`.",
IconSources.ICONS8 => "I look for an icon on Icons8.",
_ => string.Empty,
};
public static string URL(this IconSources iconSource) => iconSource switch
{
IconSources.FLAT_ICON => "https://www.flaticon.com/",
IconSources.FONT_AWESOME => "https://fontawesome.com/",
IconSources.MATERIAL_ICONS => "https://material.io/resources/icons/",
IconSources.FEATHER_ICONS => "https://feathericons.com/",
IconSources.BOOTSTRAP_ICONS => "https://icons.getbootstrap.com/",
IconSources.ICONS8 => "https://icons8.com/",
_ => string.Empty,
};
}
@@ -1,13 +0,0 @@
namespace AIStudio.Components.Pages.IconFinder;
public enum IconSources
{
GENERIC,
ICONS8,
FLAT_ICON,
FONT_AWESOME,
MATERIAL_ICONS,
FEATHER_ICONS,
BOOTSTRAP_ICONS,
}
@@ -1,19 +0,0 @@
namespace AIStudio.Components.Pages;
public static class Path
{
public const string HOME = "/";
public const string CHAT = "/chat";
public const string ABOUT = "/about";
public const string ASSISTANTS = "/assistants";
public const string SETTINGS = "/settings";
public const string SUPPORTERS = "/supporters";
public const string ASSISTANT_TRANSLATION = "/assistant/translation";
public const string ASSISTANT_REWRITE = "/assistant/rewrite-improve";
public const string ASSISTANT_ICON_FINDER = "/assistant/icons";
public const string ASSISTANT_GRAMMAR_SPELLING = "/assistant/grammar-spelling";
public const string ASSISTANT_SUMMARIZER = "/assistant/summarizer";
public const string ASSISTANT_CODING = "/assistant/coding";
public const string ASSISTANT_AGENDA = "/assistant/agenda";
}
@@ -1,12 +0,0 @@
@using AIStudio.Tools
@attribute [Route(Path.ASSISTANT_REWRITE)]
@inherits AssistantBaseCore
<MudTextField T="string" @bind-Text="@this.inputText" Validation="@this.ValidateText" AdornmentIcon="@Icons.Material.Filled.DocumentScanner" Adornment="Adornment.Start" Label="Your input to improve" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
<EnumSelection T="CommonLanguages" NameFunc="@(language => language.NameSelectingOptional())" @bind-Value="@this.selectedTargetLanguage" Icon="@Icons.Material.Filled.Translate" Label="Language" AllowOther="@true" OtherValue="CommonLanguages.OTHER" @bind-OtherInput="@this.customTargetLanguage" ValidateOther="@this.ValidateCustomLanguage" LabelOther="Custom language" />
<EnumSelection T="WritingStyles" NameFunc="@(style => style.Name())" @bind-Value="@this.selectedWritingStyle" Icon="@Icons.Material.Filled.Edit" Label="Writing style" AllowOther="@false" />
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
<MudButton Variant="Variant.Filled" Class="mb-3" OnClick="() => this.RewriteText()">
Improve
</MudButton>
@@ -1,136 +0,0 @@
using AIStudio.Chat;
using AIStudio.Tools;
namespace AIStudio.Components.Pages.RewriteImprove;
public partial class AssistantRewriteImprove : AssistantBaseCore
{
protected override string Title => "Rewrite & Improve Text";
protected override string Description =>
"""
Rewrite and improve your text. Please note, that the capabilities of the different LLM providers will vary.
""";
protected override string SystemPrompt =>
$"""
You are an expert in language and style. You receive a text as input. First, you review the text. If no
changes are needed, you return the text without modifications. If a change is necessary, you improve the
text. You can also correct spelling and grammar issues. You never add additional information. You never
ask the user for additional information. Your response only contains the improved text. You do not explain
your changes. If no changes are needed, you return the text unchanged.
The style of the text: {this.selectedWritingStyle.Prompt()}. You follow the rules according
to {this.SystemPromptLanguage()} in all your changes.
""";
protected override bool ShowResult => false;
protected override bool ShowDedicatedProgress => true;
protected override IReadOnlyList<IButtonData> FooterButtons =>
[
new ButtonData("Copy result", Icons.Material.Filled.ContentCopy, Color.Default, string.Empty, () => this.CopyToClipboard(this.rewrittenText)),
new SendToButton
{
Self = SendTo.REWRITE_ASSISTANT,
UseResultingContentBlockData = false,
GetText = () => string.IsNullOrWhiteSpace(this.rewrittenText) ? this.inputText : this.rewrittenText,
},
];
protected override ChatThread ConvertToChatThread => (this.chatThread ?? new()) with
{
SystemPrompt = SystemPrompts.DEFAULT,
};
protected override void ResetFrom()
{
this.inputText = string.Empty;
this.rewrittenText = string.Empty;
if (!this.MightPreselectValues())
{
this.selectedTargetLanguage = CommonLanguages.AS_IS;
this.customTargetLanguage = string.Empty;
this.selectedWritingStyle = WritingStyles.NOT_SPECIFIED;
}
}
protected override bool MightPreselectValues()
{
if (this.SettingsManager.ConfigurationData.RewriteImprove.PreselectOptions)
{
this.selectedTargetLanguage = this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedTargetLanguage;
this.customTargetLanguage = this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedOtherLanguage;
this.providerSettings = this.SettingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedProvider);
this.selectedWritingStyle = this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedWritingStyle;
return true;
}
return false;
}
#region Overrides of ComponentBase
protected override async Task OnInitializedAsync()
{
this.MightPreselectValues();
var deferredContent = MessageBus.INSTANCE.CheckDeferredMessages<string>(Event.SEND_TO_REWRITE_ASSISTANT).FirstOrDefault();
if (deferredContent is not null)
this.inputText = deferredContent;
await base.OnInitializedAsync();
}
#endregion
private string inputText = string.Empty;
private CommonLanguages selectedTargetLanguage;
private string customTargetLanguage = string.Empty;
private string rewrittenText = string.Empty;
private WritingStyles selectedWritingStyle;
private string? ValidateText(string text)
{
if(string.IsNullOrWhiteSpace(text))
return "Please provide a text as input. You might copy the desired text from a document or a website.";
return null;
}
private string? ValidateCustomLanguage(string language)
{
if(this.selectedTargetLanguage == CommonLanguages.OTHER && string.IsNullOrWhiteSpace(language))
return "Please provide a custom language.";
return null;
}
private string SystemPromptLanguage()
{
var lang = this.selectedTargetLanguage switch
{
CommonLanguages.AS_IS => "the source language",
CommonLanguages.OTHER => this.customTargetLanguage,
_ => $"{this.selectedTargetLanguage.Name()}",
};
if (string.IsNullOrWhiteSpace(lang))
return "the source language";
return lang;
}
private async Task RewriteText()
{
await this.form!.Validate();
if (!this.inputIsValid)
return;
this.CreateChatThread();
var time = this.AddUserRequest(this.inputText);
this.rewrittenText = await this.AddAIResponseAsync(time);
await this.JsRuntime.GenerateAndShowDiff(this.inputText, this.rewrittenText);
}
}
@@ -1,16 +0,0 @@
namespace AIStudio.Components.Pages.RewriteImprove;
public enum WritingStyles
{
NOT_SPECIFIED = 0,
EVERYDAY,
BUSINESS,
SCIENTIFIC,
JOURNALISTIC,
LITERARY,
TECHNICAL,
MARKETING,
ACADEMIC,
LEGAL,
}
@@ -1,40 +0,0 @@
namespace AIStudio.Components.Pages.RewriteImprove;
public static class WritingStylesExtensions
{
public static string Name(this WritingStyles style)
{
return style switch
{
WritingStyles.EVERYDAY => "Everyday (personal texts, social media)",
WritingStyles.BUSINESS => "Business (business emails, reports, presentations)",
WritingStyles.SCIENTIFIC => "Scientific (scientific papers, research reports)",
WritingStyles.JOURNALISTIC => "Journalistic (magazines, newspapers, news)",
WritingStyles.LITERARY => "Literary (fiction, poetry)",
WritingStyles.TECHNICAL => "Technical (manuals, documentation)",
WritingStyles.MARKETING => "Marketing (advertisements, sales texts)",
WritingStyles.ACADEMIC => "Academic (essays, seminar papers)",
WritingStyles.LEGAL => "Legal (legal texts, contracts)",
_ => "Not specified",
};
}
public static string Prompt(this WritingStyles style)
{
return style switch
{
WritingStyles.EVERYDAY => "Use a everyday style like for personal texts, social media, and informal communication.",
WritingStyles.BUSINESS => "Use a business style like for business emails, reports, and presentations. Most important is clarity and professionalism.",
WritingStyles.SCIENTIFIC => "Use a scientific style like for scientific papers, research reports, and academic writing. Most important is precision and objectivity.",
WritingStyles.JOURNALISTIC => "Use a journalistic style like for magazines, newspapers, and news. Most important is readability and engaging content.",
WritingStyles.LITERARY => "Use a literary style like for fiction, poetry, and creative writing. Most important is creativity and emotional impact.",
WritingStyles.TECHNICAL => "Use a technical style like for manuals, documentation, and technical writing. Most important is clarity and precision.",
WritingStyles.MARKETING => "Use a marketing style like for advertisements, sales texts, and promotional content. Most important is persuasiveness and engagement.",
WritingStyles.ACADEMIC => "Use a academic style like for essays, seminar papers, and academic writing. Most important is clarity and objectivity.",
WritingStyles.LEGAL => "Use a legal style like for legal texts, contracts, and official documents. Most important is precision and legal correctness. Use formal legal language.",
_ => "Keep the style of the text as it is.",
};
}
}
@@ -1,16 +0,0 @@
namespace AIStudio.Components.Pages;
public enum SendTo
{
NONE = 0,
GRAMMAR_SPELLING_ASSISTANT,
ICON_FINDER_ASSISTANT,
REWRITE_ASSISTANT,
TRANSLATION_ASSISTANT,
AGENDA_ASSISTANT,
CODING_ASSISTANT,
TEXT_SUMMARIZER_ASSISTANT,
CHAT,
}
@@ -1,22 +0,0 @@
namespace AIStudio.Components.Pages;
public static class SendToExtensions
{
public static string Name(this SendTo assistant)
{
return assistant switch
{
SendTo.GRAMMAR_SPELLING_ASSISTANT => "Grammar & Spelling Assistant",
SendTo.TEXT_SUMMARIZER_ASSISTANT => "Text Summarizer Assistant",
SendTo.ICON_FINDER_ASSISTANT => "Icon Finder Assistant",
SendTo.TRANSLATION_ASSISTANT => "Translation Assistant",
SendTo.REWRITE_ASSISTANT => "Rewrite Assistant",
SendTo.AGENDA_ASSISTANT => "Agenda Assistant",
SendTo.CODING_ASSISTANT => "Coding Assistant",
SendTo.CHAT => "New Chat",
_ => "Send to ...",
};
}
}
@@ -1,213 +0,0 @@
@attribute [Route(Path.SETTINGS)]
@using AIStudio.Components.Pages.Coding
@using AIStudio.Components.Pages.TextSummarizer
@using AIStudio.Provider
@using AIStudio.Settings
@using AIStudio.Tools
@using Host = AIStudio.Provider.SelfHosted.Host
<MudText Typo="Typo.h3" Class="mb-12">Settings</MudText>
<InnerScrolling HeaderHeight="10em">
<MudExpansionPanels Class="mb-3" MultiExpansion="@false">
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Layers" HeaderText="Configure Providers">
<MudText Typo="Typo.h4" Class="mb-3">Configured Providers</MudText>
<MudTable Items="@this.SettingsManager.ConfigurationData.Providers" 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.UsedProvider</MudTd>
<MudTd>
@if (context.UsedProvider is not Providers.SELF_HOSTED)
{
@this.GetProviderModelName(context)
}
else if (context.UsedProvider is Providers.SELF_HOSTED && context.Host is not Host.LLAMACPP)
{
@this.GetProviderModelName(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="@this.GetProviderDashboardURL(context.UsedProvider)" Target="_blank" Disabled="@(!this.HasDashboard(context.UsedProvider))">
Open Dashboard
</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Edit" Class="ma-2" OnClick="() => this.EditProvider(context)">
Edit
</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Error" StartIcon="@Icons.Material.Filled.Delete" Class="ma-2" OnClick="() => this.DeleteProvider(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.AddProvider">
Add Provider
</MudButton>
</ExpansionPanel>
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Apps" HeaderText="App Options">
<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."/>
</ExpansionPanel>
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Chat" HeaderText="Chat Options">
<ConfigurationSelect OptionDescription="Shortcut to send input" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Chat.ShortcutSendBehavior)" Data="@ConfigurationSelectDataFactory.GetSendBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Chat.ShortcutSendBehavior = selectedValue)" OptionHelp="Do you want to use any shortcut to send your input?"/>
<ConfigurationOption OptionDescription="Preselect chat options?" LabelOn="Chat options are preselected" LabelOff="No chat options are preselected" State="@(() => this.SettingsManager.ConfigurationData.Chat.PreselectOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.Chat.PreselectOptions = updatedState)" OptionHelp="When enabled, you can preselect chat options. This is might be useful when you prefer a specific provider."/>
<ConfigurationProviderSelection Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.Chat.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Chat.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Chat.PreselectedProvider = selectedValue)"/>
</ExpansionPanel>
<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?"/>
<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?"/>
</ExpansionPanel>
<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?"/>
<ConfigurationProviderSelection Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.IconFinder.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.IconFinder.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.IconFinder.PreselectedProvider = selectedValue)"/>
</MudPaper>
</ExpansionPanel>
<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)"/>
}
<ConfigurationProviderSelection Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.Translation.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Translation.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Translation.PreselectedProvider = selectedValue)"/>
</MudPaper>
</ExpansionPanel>
<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)"/>
}
<ConfigurationProviderSelection Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.Coding.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Coding.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Coding.PreselectedProvider = selectedValue)"/>
</MudPaper>
</ExpansionPanel>
<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)"/>
}
<ConfigurationProviderSelection Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.TextSummarizer.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedProvider = selectedValue)"/>
</MudPaper>
</ExpansionPanel>
<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)"/>
}
<ConfigurationProviderSelection Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Agenda.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Agenda.PreselectedProvider = selectedValue)"/>
</MudPaper>
</ExpansionPanel>
<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.GetCommonLanguagesTranslationData()" 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)"/>
}
<ConfigurationProviderSelection Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectedProvider = selectedValue)"/>
</MudPaper>
</ExpansionPanel>
<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.GetCommonLanguagesTranslationData()" 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.GetWritingStylesData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedWritingStyle = selectedValue)" OptionHelp="Which writing style should be preselected?"/>
<ConfigurationProviderSelection Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.RewriteImprove.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedProvider = selectedValue)"/>
</MudPaper>
</ExpansionPanel>
<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.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.TextContentCleaner.PreselectAgentOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.TextContentCleaner.PreselectedAgentProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.TextContentCleaner.PreselectedAgentProvider = selectedValue)"/>
</MudPaper>
</ExpansionPanel>
</MudExpansionPanels>
</InnerScrolling>
@@ -1,191 +0,0 @@
using AIStudio.Components.CommonDialogs;
using AIStudio.Provider;
using AIStudio.Settings;
using AIStudio.Tools;
using Microsoft.AspNetCore.Components;
using DialogOptions = AIStudio.Components.CommonDialogs.DialogOptions;
// ReSharper disable ClassNeverInstantiated.Global
namespace AIStudio.Components.Pages;
public partial class Settings : ComponentBase, IMessageBusReceiver, IDisposable
{
[Inject]
public SettingsManager SettingsManager { get; init; } = null!;
[Inject]
public IDialogService DialogService { get; init; } = null!;
[Inject]
public IJSRuntime JsRuntime { get; init; } = null!;
[Inject]
protected MessageBus MessageBus { get; init; } = null!;
private readonly List<ConfigurationSelectData<string>> availableProviders = new();
#region Overrides of ComponentBase
protected override async Task OnInitializedAsync()
{
// Register this component with the message bus:
this.MessageBus.RegisterComponent(this);
this.MessageBus.ApplyFilters(this, [], [ Event.CONFIGURATION_CHANGED ]);
this.UpdateProviders();
await base.OnInitializedAsync();
}
#endregion
#region Provider related
private async Task AddProvider()
{
var dialogParameters = new DialogParameters<ProviderDialog>
{
{ x => x.IsEditing, false },
};
var dialogReference = await this.DialogService.ShowAsync<ProviderDialog>("Add 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);
this.UpdateProviders();
await this.SettingsManager.StoreSettings();
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
}
private async Task EditProvider(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.DataProvider, provider.UsedProvider },
{ 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 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;
this.UpdateProviders();
await this.SettingsManager.StoreSettings();
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
}
private async Task DeleteProvider(AIStudio.Settings.Provider provider)
{
var dialogParameters = new DialogParameters
{
{ "Message", $"Are you sure you want to delete the provider '{provider.InstanceName}'?" },
};
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Delete Provider", dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result;
if (dialogResult is null || dialogResult.Canceled)
return;
var providerInstance = provider.CreateProvider();
var deleteSecretResponse = await this.SettingsManager.DeleteAPIKey(this.JsRuntime, providerInstance);
if(deleteSecretResponse.Success)
{
this.SettingsManager.ConfigurationData.Providers.Remove(provider);
await this.SettingsManager.StoreSettings();
}
this.UpdateProviders();
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
}
private bool HasDashboard(Providers provider) => provider switch
{
Providers.OPEN_AI => true,
Providers.MISTRAL => true,
Providers.ANTHROPIC => true,
Providers.FIREWORKS => true,
_ => false,
};
private string GetProviderDashboardURL(Providers provider) => provider switch
{
Providers.OPEN_AI => "https://platform.openai.com/usage",
Providers.MISTRAL => "https://console.mistral.ai/usage/",
Providers.ANTHROPIC => "https://console.anthropic.com/settings/plans",
Providers.FIREWORKS => "https://fireworks.ai/account/billing",
_ => string.Empty,
};
private string GetProviderModelName(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;
}
private void UpdateProviders()
{
this.availableProviders.Clear();
foreach (var provider in this.SettingsManager.ConfigurationData.Providers)
this.availableProviders.Add(new (provider.InstanceName, provider.Id));
}
#endregion
#region Implementation of IMessageBusReceiver
public Task ProcessMessage<TMsg>(ComponentBase? sendingComponent, Event triggeredEvent, TMsg? data)
{
switch (triggeredEvent)
{
case Event.CONFIGURATION_CHANGED:
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
}
@@ -1,48 +0,0 @@
@attribute [Route(Path.SUPPORTERS)]
<MudText Typo="Typo.h3" Class="mb-2">Supporters</MudText>
<InnerScrolling HeaderHeight="6em">
<div class="border-solid border-2 rounded-lg pa-3 mb-6">
<MudText Typo="Typo.h4" Class="mb-2">Our Titans</MudText>
<MudText Typo="Typo.body1" Style="text-align: justify; hyphens: auto;" Class="mb-3">
In this section, we highlight the titan supporters of MindWork AI Studio. Titans are prestigious companies that provide significant support to our mission.
</MudText>
<MudText Typo="Typo.body1" Style="text-align: justify; hyphens: auto;" Class="mb-3">
For companies, sponsoring MindWork AI Studio is not only a way to support innovation but also a valuable opportunity for public relations and marketing. Your company's name and logo will be featured prominently, showcasing your commitment to using cutting-edge AI tools and enhancing your reputation as an innovative enterprise.
</MudText>
<MudButton Href="https://github.com/sponsors/MindWorkAI" StartIcon="@Icons.Material.Filled.Business" Variant="Variant.Filled" Target="_blank">
Become our first Titan
</MudButton>
</div>
<MudGrid Justify="Justify.Center" Spacing="2" Style="width: 100%;" Class="align-center ma-0">
<MudItem Class="pa-0 pr-2" xs="6">
<MudPaper Elevation="3" Class="border-solid border rounded-lg pa-3">
<MudText Typo="Typo.h4" Class="mb-3 d-flex align-center">
<MudIcon Icon="@Icons.Material.Filled.People" Size="Size.Large" class="mr-2"/>
Individual Contributors
</MudText>
<MudButton Href="https://github.com/sponsors/MindWorkAI" StartIcon="@Icons.Material.Filled.People" Variant="Variant.Filled" Target="_blank">
Become a contributor
</MudButton>
</MudPaper>
</MudItem>
<MudItem xs="6" Class="pa-0 pl-2">
<MudPaper Elevation="3" Class="border-solid border rounded-lg pa-3">
<MudText Typo="Typo.h4" Class="mb-3 d-flex align-center">
<MudIcon Icon="@Icons.Material.Filled.Business" Size="Size.Large" class="mr-2"/>
Business Contributors
</MudText>
<MudButton Href="https://github.com/sponsors/MindWorkAI" StartIcon="@Icons.Material.Filled.Business" Variant="Variant.Filled" Target="_blank">
Become a contributor
</MudButton>
</MudPaper>
</MudItem>
</MudGrid>
</InnerScrolling>
@@ -1,7 +0,0 @@
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components.Pages;
public partial class Supporters : ComponentBase
{
}
@@ -1,17 +0,0 @@
@attribute [Route(Path.ASSISTANT_SUMMARIZER)]
@using AIStudio.Tools
@inherits AssistantBaseCore
@if (!this.SettingsManager.ConfigurationData.TextSummarizer.HideWebContentReader)
{
<ReadWebContent @bind-Content="@this.inputText" ProviderSettings="@this.providerSettings" @bind-AgentIsRunning="@this.isAgentRunning" Preselect="@(this.SettingsManager.ConfigurationData.TextSummarizer.PreselectOptions && this.SettingsManager.ConfigurationData.TextSummarizer.PreselectWebContentReader)" PreselectContentCleanerAgent="@(this.SettingsManager.ConfigurationData.TextSummarizer.PreselectOptions && this.SettingsManager.ConfigurationData.TextSummarizer.PreselectContentCleanerAgent)"/>
}
<MudTextField T="string" Disabled="@this.isAgentRunning" @bind-Text="@this.inputText" Validation="@this.ValidatingText" AdornmentIcon="@Icons.Material.Filled.DocumentScanner" Adornment="Adornment.Start" Label="Your input" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
<EnumSelection T="CommonLanguages" NameFunc="@(language => language.Name())" @bind-Value="@this.selectedTargetLanguage" Icon="@Icons.Material.Filled.Translate" Label="Target language" AllowOther="@true" @bind-OtherInput="@this.customTargetLanguage" OtherValue="CommonLanguages.OTHER" LabelOther="Custom target language" ValidateOther="@this.ValidateCustomLanguage" />
<EnumSelection T="Complexity" NameFunc="@(complexity => complexity.Name())" @bind-Value="@this.selectedComplexity" Icon="@Icons.Material.Filled.Layers" Label="Target complexity" AllowOther="@true" @bind-OtherInput="@this.expertInField" OtherValue="Complexity.SCIENTIFIC_LANGUAGE_OTHER_EXPERTS" LabelOther="Your expertise" ValidateOther="@this.ValidateExpertInField" />
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
<MudButton Variant="Variant.Filled" Class="mb-3" OnClick="() => this.SummarizeText()" Disabled="@this.isAgentRunning">
Summarize
</MudButton>
@@ -1,133 +0,0 @@
using AIStudio.Chat;
using AIStudio.Tools;
namespace AIStudio.Components.Pages.TextSummarizer;
public partial class AssistantTextSummarizer : AssistantBaseCore
{
protected override string Title => "Text Summarizer";
protected override string Description =>
"""
Summarize long text into a shorter version while retaining the main points.
You might want to change the language of the summary to make it more readable.
It is also possible to change the complexity of the summary to make it
easy to understand.
""";
protected override string SystemPrompt =>
"""
You get a long text as input. The user wants to get a summary of the text.
The user might want to change the language of the summary. In this case,
you should provide a summary in the requested language. Eventually, the user
want to change the complexity of the text. In this case, you should provide
a summary with the requested complexity. In any case, do not add any information.
""";
protected override IReadOnlyList<IButtonData> FooterButtons =>
[
new SendToButton
{
Self = SendTo.TEXT_SUMMARIZER_ASSISTANT,
},
];
protected override ChatThread ConvertToChatThread => (this.chatThread ?? new()) with
{
SystemPrompt = SystemPrompts.DEFAULT,
};
protected override void ResetFrom()
{
this.inputText = string.Empty;
if(!this.MightPreselectValues())
{
this.selectedTargetLanguage = CommonLanguages.AS_IS;
this.customTargetLanguage = string.Empty;
this.selectedComplexity = Complexity.NO_CHANGE;
this.expertInField = string.Empty;
}
}
protected override bool MightPreselectValues()
{
if (this.SettingsManager.ConfigurationData.TextSummarizer.PreselectOptions)
{
this.selectedTargetLanguage = this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedTargetLanguage;
this.customTargetLanguage = this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedOtherLanguage;
this.selectedComplexity = this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedComplexity;
this.expertInField = this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedExpertInField;
this.providerSettings = this.SettingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedProvider);
return true;
}
return false;
}
private string inputText = string.Empty;
private bool isAgentRunning;
private CommonLanguages selectedTargetLanguage;
private string customTargetLanguage = string.Empty;
private Complexity selectedComplexity;
private string expertInField = string.Empty;
#region Overrides of ComponentBase
protected override async Task OnInitializedAsync()
{
this.MightPreselectValues();
var deferredContent = MessageBus.INSTANCE.CheckDeferredMessages<string>(Event.SEND_TO_TEXT_SUMMARIZER_ASSISTANT).FirstOrDefault();
if (deferredContent is not null)
this.inputText = deferredContent;
await base.OnInitializedAsync();
}
#endregion
private string? ValidatingText(string text)
{
if(string.IsNullOrWhiteSpace(text))
return "Please provide a text as input. You might copy the desired text from a document or a website.";
return null;
}
private string? ValidateCustomLanguage(string language)
{
if(this.selectedTargetLanguage == CommonLanguages.OTHER && string.IsNullOrWhiteSpace(language))
return "Please provide a custom language.";
return null;
}
private string? ValidateExpertInField(string field)
{
if(this.selectedComplexity == Complexity.SCIENTIFIC_LANGUAGE_OTHER_EXPERTS && string.IsNullOrWhiteSpace(field))
return "Please provide your field of expertise.";
return null;
}
private async Task SummarizeText()
{
await this.form!.Validate();
if (!this.inputIsValid)
return;
this.CreateChatThread();
var time = this.AddUserRequest(
$"""
{this.selectedTargetLanguage.PromptSummarizing(this.customTargetLanguage)}
{this.selectedComplexity.Prompt(this.expertInField)}
Please summarize the following text:
```
{this.inputText}
```
""");
await this.AddAIResponseAsync(time);
}
}
@@ -1,13 +0,0 @@
namespace AIStudio.Components.Pages.TextSummarizer;
public enum Complexity
{
NO_CHANGE,
SIMPLE_LANGUAGE,
TEEN_LANGUAGE,
EVERYDAY_LANGUAGE,
POPULAR_SCIENCE_LANGUAGE,
SCIENTIFIC_LANGUAGE_FIELD_EXPERTS,
SCIENTIFIC_LANGUAGE_OTHER_EXPERTS,
}
@@ -1,32 +0,0 @@
namespace AIStudio.Components.Pages.TextSummarizer;
public static class ComplexityExtensions
{
public static string Name(this Complexity complexity) => complexity switch
{
Complexity.NO_CHANGE => "No change in complexity",
Complexity.SIMPLE_LANGUAGE => "Simple language, e.g., for children",
Complexity.TEEN_LANGUAGE => "Teen language, e.g., for teenagers",
Complexity.EVERYDAY_LANGUAGE => "Everyday language, e.g., for adults",
Complexity.POPULAR_SCIENCE_LANGUAGE => "Popular science language, e.g., for people interested in science",
Complexity.SCIENTIFIC_LANGUAGE_FIELD_EXPERTS => "Scientific language for experts in this field",
Complexity.SCIENTIFIC_LANGUAGE_OTHER_EXPERTS => "Scientific language for experts from other fields (interdisciplinary)",
_ => "No change in complexity",
};
public static string Prompt(this Complexity complexity, string expertInField) => complexity switch
{
Complexity.NO_CHANGE => "Do not change the complexity of the text.",
Complexity.SIMPLE_LANGUAGE => "Simplify the language, e.g., for 8 to 12-year-old children. Might use short sentences and simple words. You could use analogies to explain complex terms.",
Complexity.TEEN_LANGUAGE => "Use a language suitable for teenagers, e.g., 16 to 19 years old. Might use teenage slang and analogies to explain complex terms.",
Complexity.EVERYDAY_LANGUAGE => "Use everyday language suitable for adults. Avoid specific scientific terms. Use everday analogies to explain complex terms.",
Complexity.POPULAR_SCIENCE_LANGUAGE => "Use popular science language, e.g., for people interested in science. The text should be easy to understand, though. Use analogies to explain complex terms.",
Complexity.SCIENTIFIC_LANGUAGE_FIELD_EXPERTS => "Use scientific language for experts in the field of the texts subject. Use specific terms for this field.",
Complexity.SCIENTIFIC_LANGUAGE_OTHER_EXPERTS => $"The reader is an expert in {expertInField}. Change the language so that it is suitable. Explain specific terms, so that the reader within his field can understand the text. You might use analogies to explain complex terms.",
_ => "Do not change the complexity of the text.",
};
}
@@ -1,25 +0,0 @@
@attribute [Route(Path.ASSISTANT_TRANSLATION)]
@using AIStudio.Tools
@inherits AssistantBaseCore
@if (!this.SettingsManager.ConfigurationData.Translation.HideWebContentReader)
{
<ReadWebContent @bind-Content="@this.inputText" ProviderSettings="@this.providerSettings" @bind-AgentIsRunning="@this.isAgentRunning" Preselect="@(this.SettingsManager.ConfigurationData.Translation.PreselectOptions && this.SettingsManager.ConfigurationData.Translation.PreselectWebContentReader)" PreselectContentCleanerAgent="@(this.SettingsManager.ConfigurationData.Translation.PreselectOptions && this.SettingsManager.ConfigurationData.Translation.PreselectContentCleanerAgent)"/>
}
<MudTextSwitch Label="Live translation" @bind-Value="@this.liveTranslation" LabelOn="Live translation" LabelOff="No live translation"/>
@if (this.liveTranslation)
{
<MudTextField T="string" Disabled="@this.isAgentRunning" @bind-Text="@this.inputText" Validation="@this.ValidatingText" AdornmentIcon="@Icons.Material.Filled.DocumentScanner" Adornment="Adornment.Start" Label="Your input" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" Immediate="@true" DebounceInterval="@this.SettingsManager.ConfigurationData.Translation.DebounceIntervalMilliseconds" OnDebounceIntervalElapsed="() => this.TranslateText(force: false)" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
}
else
{
<MudTextField T="string" Disabled="@this.isAgentRunning" @bind-Text="@this.inputText" Validation="@this.ValidatingText" AdornmentIcon="@Icons.Material.Filled.DocumentScanner" Adornment="Adornment.Start" Label="Your input" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
}
<EnumSelection T="CommonLanguages" NameFunc="@(language => language.NameSelecting())" @bind-Value="@this.selectedTargetLanguage" ValidateSelection="@this.ValidatingTargetLanguage" Icon="@Icons.Material.Filled.Translate" Label="Target language" AllowOther="@true" OtherValue="CommonLanguages.OTHER" @bind-OtherInput="@this.customTargetLanguage" ValidateOther="@this.ValidateCustomLanguage" LabelOther="Custom target language" />
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
<MudButton Disabled="@this.isAgentRunning" Variant="Variant.Filled" Class="mb-3" OnClick="() => this.TranslateText(force: true)">
Translate
</MudButton>
@@ -1,130 +0,0 @@
using AIStudio.Chat;
using AIStudio.Tools;
namespace AIStudio.Components.Pages.Translation;
public partial class AssistantTranslation : AssistantBaseCore
{
protected override string Title => "Translation";
protected override string Description =>
"""
Translate text from one language to another.
""";
protected override string SystemPrompt =>
"""
You get text in a source language as input. The user wants to get the text translated into a target language.
Provide the translation in the requested language. Do not add any information. Correct any spelling or grammar mistakes.
Do not ask for additional information. Do not mirror the user's language. Do not mirror the task. When the target
language requires, e.g., shorter sentences, you should split the text into shorter sentences.
""";
protected override IReadOnlyList<IButtonData> FooterButtons =>
[
new SendToButton
{
Self = SendTo.TRANSLATION_ASSISTANT,
},
];
protected override ChatThread ConvertToChatThread => (this.chatThread ?? new()) with
{
SystemPrompt = SystemPrompts.DEFAULT,
};
protected override void ResetFrom()
{
this.inputText = string.Empty;
this.inputTextLastTranslation = string.Empty;
if (!this.MightPreselectValues())
{
this.liveTranslation = false;
this.selectedTargetLanguage = CommonLanguages.AS_IS;
this.customTargetLanguage = string.Empty;
}
}
protected override bool MightPreselectValues()
{
if (this.SettingsManager.ConfigurationData.Translation.PreselectOptions)
{
this.liveTranslation = this.SettingsManager.ConfigurationData.Translation.PreselectLiveTranslation;
this.selectedTargetLanguage = this.SettingsManager.ConfigurationData.Translation.PreselectedTargetLanguage;
this.customTargetLanguage = this.SettingsManager.ConfigurationData.Translation.PreselectOtherLanguage;
this.providerSettings = this.SettingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == this.SettingsManager.ConfigurationData.Translation.PreselectedProvider);
return true;
}
return false;
}
private bool liveTranslation;
private bool isAgentRunning;
private string inputText = string.Empty;
private string inputTextLastTranslation = string.Empty;
private CommonLanguages selectedTargetLanguage;
private string customTargetLanguage = string.Empty;
#region Overrides of ComponentBase
protected override async Task OnInitializedAsync()
{
this.MightPreselectValues();
var deferredContent = MessageBus.INSTANCE.CheckDeferredMessages<string>(Event.SEND_TO_TRANSLATION_ASSISTANT).FirstOrDefault();
if (deferredContent is not null)
this.inputText = deferredContent;
await base.OnInitializedAsync();
}
#endregion
private string? ValidatingText(string text)
{
if(string.IsNullOrWhiteSpace(text))
return "Please provide a text as input. You might copy the desired text from a document or a website.";
return null;
}
private string? ValidatingTargetLanguage(CommonLanguages language)
{
if(language == CommonLanguages.AS_IS)
return "Please select a target language.";
return null;
}
private string? ValidateCustomLanguage(string language)
{
if(this.selectedTargetLanguage == CommonLanguages.OTHER && string.IsNullOrWhiteSpace(language))
return "Please provide a custom language.";
return null;
}
private async Task TranslateText(bool force)
{
await this.form!.Validate();
if (!this.inputIsValid)
return;
if(!force && this.inputText == this.inputTextLastTranslation)
return;
this.inputTextLastTranslation = this.inputText;
this.CreateChatThread();
var time = this.AddUserRequest(
$"""
{this.selectedTargetLanguage.PromptTranslation(this.customTargetLanguage)}
The given text is:
---
{this.inputText}
""");
await this.AddAIResponseAsync(time);
}
}
@@ -2,7 +2,7 @@ using AIStudio.Tools;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components.Blocks;
namespace AIStudio.Components;
public partial class ProcessComponent<T> : ComponentBase where T : struct, Enum
{
@@ -2,7 +2,7 @@ using AIStudio.Settings;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components.Blocks;
namespace AIStudio.Components;
public partial class ProviderSelection : ComponentBase
{
@@ -5,7 +5,7 @@ using AIStudio.Tools;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components.Blocks;
namespace AIStudio.Components;
public partial class ReadWebContent : ComponentBase
{
@@ -1,4 +1,4 @@
namespace AIStudio.Components.Blocks;
namespace AIStudio.Components;
public enum ReadWebContentSteps
{
Loaded 100 of 137 files, more files were not shown because too many files have changed in this diff. Show more