Assistant send result (#75)

This commit is contained in:
Thorsten Sommer 2024-08-18 12:32:18 +02:00 committed by GitHub
parent ea0ead58f8
commit 909fbe89bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 400 additions and 63 deletions

View File

@ -1,4 +1,6 @@
@using AIStudio.Chat @using AIStudio.Chat
@using AIStudio.Components.Pages
@using AIStudio.Tools
<MudText Typo="Typo.h3" Class="mb-2 mr-3"> <MudText Typo="Typo.h3" Class="mb-2 mr-3">
@this.Title @this.Title
</MudText> </MudText>
@ -34,11 +36,11 @@
@if (this.FooterButtons.Count > 0) @if (this.FooterButtons.Count > 0)
{ {
<MudStack Row="@true" Wrap="Wrap.Wrap" Class="mt-3 mr-2"> <MudStack Row="@true" Wrap="Wrap.Wrap" Class="mt-3 mr-2">
@foreach (var buttonData in this.FooterButtons) @foreach (var button in this.FooterButtons)
{ {
switch (buttonData) switch (button)
{ {
case var _ when !string.IsNullOrWhiteSpace(buttonData.Tooltip): case ButtonData buttonData when !string.IsNullOrWhiteSpace(buttonData.Tooltip):
<MudTooltip Text="@buttonData.Tooltip"> <MudTooltip Text="@buttonData.Tooltip">
<MudButton Variant="Variant.Filled" Color="@buttonData.Color" StartIcon="@GetButtonIcon(buttonData.Icon)" OnClick="async () => await buttonData.AsyncAction()"> <MudButton Variant="Variant.Filled" Color="@buttonData.Color" StartIcon="@GetButtonIcon(buttonData.Icon)" OnClick="async () => await buttonData.AsyncAction()">
@buttonData.Text @buttonData.Text
@ -46,11 +48,25 @@
</MudTooltip> </MudTooltip>
break; break;
default: case ButtonData buttonData:
<MudButton Variant="Variant.Filled" Color="@buttonData.Color" StartIcon="@GetButtonIcon(buttonData.Icon)" OnClick="async () => await buttonData.AsyncAction()"> <MudButton Variant="Variant.Filled" Color="@buttonData.Color" StartIcon="@GetButtonIcon(buttonData.Icon)" OnClick="async () => await buttonData.AsyncAction()">
@buttonData.Text @buttonData.Text
</MudButton> </MudButton>
break; 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<SendToAssistant>().OrderBy(n => n.Name().Length))
{
if(assistant is Pages.SendToAssistant.NONE || sendToButton.Self == assistant)
continue;
<MudMenuItem OnClick="() => this.SendToAssistant(assistant, sendToButton)">
@assistant.Name()
</MudMenuItem>
}
</MudMenu>
break;
} }
} }
</MudStack> </MudStack>

View File

@ -1,10 +1,13 @@
using AIStudio.Chat; using AIStudio.Chat;
using AIStudio.Components.Pages;
using AIStudio.Provider; using AIStudio.Provider;
using AIStudio.Settings; using AIStudio.Settings;
using AIStudio.Tools; using AIStudio.Tools;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Path = AIStudio.Components.Pages.Path;
namespace AIStudio.Components; namespace AIStudio.Components;
public abstract partial class AssistantBase : ComponentBase public abstract partial class AssistantBase : ComponentBase
@ -24,6 +27,9 @@ public abstract partial class AssistantBase : ComponentBase
[Inject] [Inject]
protected Rust Rust { get; init; } = null!; protected Rust Rust { get; init; } = null!;
[Inject]
protected NavigationManager NavigationManager { get; init; } = null!;
internal const string AFTER_RESULT_DIV_ID = "afterAssistantResult"; internal const string AFTER_RESULT_DIV_ID = "afterAssistantResult";
internal const string ASSISTANT_RESULT_DIV_ID = "assistantResult"; internal const string ASSISTANT_RESULT_DIV_ID = "assistantResult";
@ -39,7 +45,7 @@ public abstract partial class AssistantBase : ComponentBase
protected virtual bool ShowDedicatedProgress => false; protected virtual bool ShowDedicatedProgress => false;
protected virtual IReadOnlyList<ButtonData> FooterButtons => []; protected virtual IReadOnlyList<IButtonData> FooterButtons => [];
protected static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new(); protected static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new();
@ -157,4 +163,34 @@ public abstract partial class AssistantBase : ComponentBase
return icon; return icon;
} }
private Task SendToAssistant(SendToAssistant assistant, SendToButton sendToButton)
{
var contentToSend = sendToButton.UseResultingContentBlockData switch
{
false => sendToButton.GetData(),
true => this.resultingContentBlock?.Content switch
{
ContentText textBlock => textBlock.Text,
_ => string.Empty,
},
};
var (eventItem, path) = assistant switch
{
Pages.SendToAssistant.AGENDA_ASSISTANT => (Event.SEND_TO_AGENDA_ASSISTANT, Path.ASSISTANT_AGENDA),
Pages.SendToAssistant.CODING_ASSISTANT => (Event.SEND_TO_CODING_ASSISTANT, Path.ASSISTANT_CODING),
Pages.SendToAssistant.REWRITE_ASSISTANT => (Event.SEND_TO_REWRITE_ASSISTANT, Path.ASSISTANT_REWRITE),
Pages.SendToAssistant.TRANSLATION_ASSISTANT => (Event.SEND_TO_TRANSLATION_ASSISTANT, Path.ASSISTANT_TRANSLATION),
Pages.SendToAssistant.ICON_FINDER_ASSISTANT => (Event.SEND_TO_ICON_FINDER_ASSISTANT, Path.ASSISTANT_ICON_FINDER),
Pages.SendToAssistant.GRAMMAR_SPELLING_ASSISTANT => (Event.SEND_TO_GRAMMAR_SPELLING_ASSISTANT, Path.ASSISTANT_GRAMMAR_SPELLING),
Pages.SendToAssistant.TEXT_SUMMARIZER_ASSISTANT => (Event.SEND_TO_TEXT_SUMMARIZER_ASSISTANT, Path.ASSISTANT_SUMMARIZER),
_ => (Event.NONE, Path.ASSISTANTS),
};
MessageBus.INSTANCE.DeferMessage(this, eventItem, contentToSend);
this.NavigationManager.NavigateTo(path);
return Task.CompletedTask;
}
} }

View File

@ -0,0 +1,24 @@
@typeparam T
<MudButtonGroup Color="@this.Color" Variant="Variant.Filled">
@if (string.IsNullOrWhiteSpace(this.Icon))
{
<MudButton OnClick="() => this.OnClick(this.selectedValue)">
@this.SelectedValueName()
</MudButton>
}
else
{
<MudButton StartIcon="@this.Icon" OnClick="() => this.OnClick(this.selectedValue)">
@this.SelectedValueName()
</MudButton>
}
<MudMenu Icon="@Icons.Material.Filled.ArrowDropDown" Style="align-self: auto;">
@foreach(var item in this.Items)
{
<MudMenuItem OnClick="() => this.SelectItem(item)">
@this.NameFunc(item)
</MudMenuItem>
}
</MudMenu>
</MudButtonGroup>

View File

@ -0,0 +1,64 @@
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components.Blocks;
public partial class SplitButton<T> : ComponentBase
{
[Parameter]
public Color Color { get; set; } = Color.Default;
[Parameter]
public string Icon { get; set; } = string.Empty;
[Parameter]
public IReadOnlyCollection<T> Items { get; set; } = [];
[Parameter]
public Func<T, string> NameFunc { get; set; } = _ => string.Empty;
[Parameter]
public T? PreselectedValue { get; set; }
[Parameter]
public Func<T?, Task> OnClick { get; set; } = _ => Task.CompletedTask;
/// <summary>
/// What happens when the user selects an item by the dropdown?
/// Immediate = true means that the OnClick event is triggered immediately
/// after the user selects an item. Immediate = false means that the OnClick
/// event is triggered only when the user clicks the button.
/// </summary>
[Parameter]
public bool Immediate { get; set; }
#region Overrides of ComponentBase
protected override async Task OnInitializedAsync()
{
if(this.PreselectedValue is not null)
this.selectedValue = this.PreselectedValue;
else
this.selectedValue = this.Items.FirstOrDefault();
await base.OnInitializedAsync();
}
#endregion
private T? selectedValue;
private string SelectedValueName()
{
if(this.selectedValue is null)
return "Select...";
return this.NameFunc(this.selectedValue!);
}
private void SelectItem(T item)
{
this.selectedValue = item;
if(this.Immediate)
this.OnClick(this.selectedValue);
}
}

View File

@ -51,12 +51,12 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDis
private static readonly IReadOnlyCollection<NavBarItem> NAV_ITEMS = new List<NavBarItem> private static readonly IReadOnlyCollection<NavBarItem> NAV_ITEMS = new List<NavBarItem>
{ {
new("Home", Icons.Material.Filled.Home, Color.Default, "/", true), new("Home", Icons.Material.Filled.Home, Color.Default, Pages.Path.HOME, true),
new("Chat", Icons.Material.Filled.Chat, Color.Default, "/chat", false), new("Chat", Icons.Material.Filled.Chat, Color.Default, Pages.Path.CHAT, false),
new("Assistants", Icons.Material.Filled.Apps, Color.Default ,"/assistants", false), new("Assistants", Icons.Material.Filled.Apps, Color.Default, Pages.Path.ASSISTANTS, false),
new("Supporters", Icons.Material.Filled.Favorite, Color.Error ,"/supporters", false), new("Supporters", Icons.Material.Filled.Favorite, Color.Error, Pages.Path.SUPPORTERS, false),
new("About", Icons.Material.Filled.Info, Color.Default ,"/about", false), new("About", Icons.Material.Filled.Info, Color.Default, Pages.Path.ABOUT, false),
new("Settings", Icons.Material.Filled.Settings, Color.Default ,"/settings", false), new("Settings", Icons.Material.Filled.Settings, Color.Default, Pages.Path.SETTINGS, false),
}; };
#region Overrides of ComponentBase #region Overrides of ComponentBase

View File

@ -1,4 +1,4 @@
@page "/about" @attribute [Route(Path.ABOUT)]
@using AIStudio.Tools @using AIStudio.Tools
<MudText Typo="Typo.h3" Class="mb-2">About MindWork AI Studio</MudText> <MudText Typo="Typo.h3" Class="mb-2">About MindWork AI Studio</MudText>

View File

@ -1,4 +1,4 @@
@page "/assistant/agenda" @attribute [Route(Path.ASSISTANT_AGENDA)]
@using AIStudio.Tools @using AIStudio.Tools
@inherits AssistantBaseCore @inherits AssistantBaseCore

View File

@ -93,6 +93,14 @@ public partial class AssistantAgenda : AssistantBaseCore
- Mary Jane: Work package 3 - Mary Jane: Work package 3
"""; """;
protected override IReadOnlyList<IButtonData> FooterButtons =>
[
new SendToButton
{
Self = SendToAssistant.AGENDA_ASSISTANT,
},
];
private string inputTopic = string.Empty; private string inputTopic = string.Empty;
private string inputName = string.Empty; private string inputName = string.Empty;
private string inputContent = string.Empty; private string inputContent = string.Empty;
@ -145,6 +153,10 @@ public partial class AssistantAgenda : AssistantBaseCore
this.providerSettings = this.SettingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == this.SettingsManager.ConfigurationData.Agenda.PreselectedProvider); this.providerSettings = this.SettingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == this.SettingsManager.ConfigurationData.Agenda.PreselectedProvider);
} }
var deferredContent = MessageBus.INSTANCE.CheckDeferredMessages<string>(Event.SEND_TO_AGENDA_ASSISTANT).FirstOrDefault();
if (deferredContent is not null)
this.inputContent = deferredContent;
await base.OnInitializedAsync(); await base.OnInitializedAsync();
} }

View File

@ -1,4 +1,4 @@
@page "/assistants" @attribute [Route(Path.ASSISTANTS)]
<MudText Typo="Typo.h3" Class="mb-2 mr-3"> <MudText Typo="Typo.h3" Class="mb-2 mr-3">
Assistants Assistants
@ -10,25 +10,25 @@
General General
</MudText> </MudText>
<MudStack Row="@true" Wrap="@Wrap.Wrap" Class="mb-3"> <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="/assistant/summarizer"/> <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="/assistant/translation"/> <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="/assistant/grammar-spelling"/> <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="/assistant/rewrite-improve"/> <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> </MudStack>
<MudText Typo="Typo.h4" Class="mb-2 mr-3 mt-6"> <MudText Typo="Typo.h4" Class="mb-2 mr-3 mt-6">
Business Business
</MudText> </MudText>
<MudStack Row="@true" Wrap="@Wrap.Wrap" Class="mb-3"> <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="/assistant/agenda"/> <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="/assistant/icons"/> <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> </MudStack>
<MudText Typo="Typo.h4" Class="mb-2 mr-3 mt-6"> <MudText Typo="Typo.h4" Class="mb-2 mr-3 mt-6">
Software Engineering Software Engineering
</MudText> </MudText>
<MudStack Row="@true" Wrap="@Wrap.Wrap" Class="mb-3"> <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="/assistant/coding"/> <AssistantBlock Name="Coding" Description="Get coding and debugging support from a LLM." Icon="@Icons.Material.Filled.Code" Link="@Path.ASSISTANT_CODING"/>
</MudStack> </MudStack>
</InnerScrolling> </InnerScrolling>

View File

@ -1,4 +1,4 @@
@page "/chat" @attribute [Route(Path.CHAT)]
@using AIStudio.Chat @using AIStudio.Chat
@using AIStudio.Settings.DataModel @using AIStudio.Settings.DataModel

View File

@ -250,11 +250,11 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable
string chatPath; string chatPath;
if (this.chatThread.WorkspaceId == Guid.Empty) if (this.chatThread.WorkspaceId == Guid.Empty)
{ {
chatPath = Path.Join(SettingsManager.DataDirectory, "tempChats", this.chatThread.ChatId.ToString()); chatPath = System.IO.Path.Join(SettingsManager.DataDirectory, "tempChats", this.chatThread.ChatId.ToString());
} }
else else
{ {
chatPath = Path.Join(SettingsManager.DataDirectory, "workspaces", this.chatThread.WorkspaceId.ToString(), this.chatThread.ChatId.ToString()); 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); await this.workspaces.DeleteChat(chatPath, askForConfirmation: false, unloadChat: true);
@ -327,12 +327,12 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable
if (this.chatThread!.WorkspaceId == Guid.Empty) if (this.chatThread!.WorkspaceId == Guid.Empty)
{ {
// Case: The chat is stored in the temporary storage: // Case: The chat is stored in the temporary storage:
await this.workspaces.DeleteChat(Path.Join(SettingsManager.DataDirectory, "tempChats", this.chatThread.ChatId.ToString()), askForConfirmation: false, unloadChat: false); await this.workspaces.DeleteChat(System.IO.Path.Join(SettingsManager.DataDirectory, "tempChats", this.chatThread.ChatId.ToString()), askForConfirmation: false, unloadChat: false);
} }
else else
{ {
// Case: The chat is stored in a workspace. // Case: The chat is stored in a workspace.
await this.workspaces.DeleteChat(Path.Join(SettingsManager.DataDirectory, "workspaces", this.chatThread.WorkspaceId.ToString(), this.chatThread.ChatId.ToString()), askForConfirmation: false, unloadChat: false); await 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; this.chatThread!.WorkspaceId = workspaceId;

View File

@ -1,4 +1,4 @@
@page "/assistant/coding" @attribute [Route(Path.ASSISTANT_CODING)]
@inherits AssistantBaseCore @inherits AssistantBaseCore
<MudExpansionPanels Class="mb-3"> <MudExpansionPanels Class="mb-3">

View File

@ -1,5 +1,7 @@
using System.Text; using System.Text;
using AIStudio.Tools;
namespace AIStudio.Components.Pages.Coding; namespace AIStudio.Components.Pages.Coding;
public partial class AssistantCoding : AssistantBaseCore public partial class AssistantCoding : AssistantBaseCore
@ -24,6 +26,14 @@ public partial class AssistantCoding : AssistantBaseCore
When the user asks in a different language than English, you answer in the same language! When the user asks in a different language than English, you answer in the same language!
"""; """;
protected override IReadOnlyList<IButtonData> FooterButtons =>
[
new SendToButton
{
Self = SendToAssistant.CODING_ASSISTANT,
},
];
private readonly List<CodingContext> codingContexts = new(); private readonly List<CodingContext> codingContexts = new();
private bool provideCompilerMessages; private bool provideCompilerMessages;
private string compilerMessages = string.Empty; private string compilerMessages = string.Empty;
@ -39,6 +49,10 @@ public partial class AssistantCoding : AssistantBaseCore
this.providerSettings = this.SettingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == this.SettingsManager.ConfigurationData.Coding.PreselectedProvider); this.providerSettings = this.SettingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == this.SettingsManager.ConfigurationData.Coding.PreselectedProvider);
} }
var deferredContent = MessageBus.INSTANCE.CheckDeferredMessages<string>(Event.SEND_TO_CODING_ASSISTANT).FirstOrDefault();
if (deferredContent is not null)
this.questions = deferredContent;
await base.OnInitializedAsync(); await base.OnInitializedAsync();
} }

View File

@ -1,5 +1,5 @@
@using AIStudio.Tools @using AIStudio.Tools
@page "/assistant/grammar-spelling" @attribute [Route(Path.ASSISTANT_GRAMMAR_SPELLING)]
@inherits AssistantBaseCore @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"/> <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"/>

View File

@ -25,10 +25,16 @@ public partial class AssistantGrammarSpelling : AssistantBaseCore
protected override bool ShowDedicatedProgress => true; protected override bool ShowDedicatedProgress => true;
protected override IReadOnlyList<ButtonData> FooterButtons => new[] protected override IReadOnlyList<IButtonData> FooterButtons =>
{ [
new ButtonData("Copy result", Icons.Material.Filled.ContentCopy, Color.Default, string.Empty, () => this.CopyToClipboard(this.correctedText)), new ButtonData("Copy result", Icons.Material.Filled.ContentCopy, Color.Default, string.Empty, () => this.CopyToClipboard(this.correctedText)),
}; new SendToButton
{
Self = SendToAssistant.GRAMMAR_SPELLING_ASSISTANT,
UseResultingContentBlockData = false,
GetData = () => string.IsNullOrWhiteSpace(this.correctedText) ? this.inputText : this.correctedText
},
];
#region Overrides of ComponentBase #region Overrides of ComponentBase
@ -41,6 +47,10 @@ public partial class AssistantGrammarSpelling : AssistantBaseCore
this.providerSettings = this.SettingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectedProvider); this.providerSettings = this.SettingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectedProvider);
} }
var deferredContent = MessageBus.INSTANCE.CheckDeferredMessages<string>(Event.SEND_TO_GRAMMAR_SPELLING_ASSISTANT).FirstOrDefault();
if (deferredContent is not null)
this.inputText = deferredContent;
await base.OnInitializedAsync(); await base.OnInitializedAsync();
} }

View File

@ -1,4 +1,4 @@
@page "/" @attribute [Route(Path.HOME)]
@using AIStudio.Tools @using AIStudio.Tools
<MudImage Src="svg/banner.svg" /> <MudImage Src="svg/banner.svg" />

View File

@ -1,4 +1,4 @@
@page "/assistant/icons" @attribute [Route(Path.ASSISTANT_ICON_FINDER)]
@inherits AssistantBaseCore @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"/> <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"/>

View File

@ -1,3 +1,5 @@
using AIStudio.Tools;
namespace AIStudio.Components.Pages.IconFinder; namespace AIStudio.Components.Pages.IconFinder;
public partial class AssistantIconFinder : AssistantBaseCore public partial class AssistantIconFinder : AssistantBaseCore
@ -5,21 +7,6 @@ public partial class AssistantIconFinder : AssistantBaseCore
private string inputContext = string.Empty; private string inputContext = string.Empty;
private IconSources selectedIconSource; private IconSources selectedIconSource;
#region Overrides of ComponentBase
protected override async Task OnInitializedAsync()
{
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);
}
await base.OnInitializedAsync();
}
#endregion
protected override string Title => "Icon Finder"; protected override string Title => "Icon Finder";
protected override string Description => protected override string Description =>
@ -41,6 +28,33 @@ public partial class AssistantIconFinder : AssistantBaseCore
quotation marks. quotation marks.
"""; """;
protected override IReadOnlyList<IButtonData> FooterButtons =>
[
new SendToButton
{
Self = SendToAssistant.ICON_FINDER_ASSISTANT,
},
];
#region Overrides of ComponentBase
protected override async Task OnInitializedAsync()
{
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);
}
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) private string? ValidatingContext(string context)
{ {
if(string.IsNullOrWhiteSpace(context)) if(string.IsNullOrWhiteSpace(context))

View File

@ -0,0 +1,19 @@
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";
}

View File

@ -1,5 +1,5 @@
@using AIStudio.Tools @using AIStudio.Tools
@page "/assistant/rewrite-improve" @attribute [Route(Path.ASSISTANT_REWRITE)]
@inherits AssistantBaseCore @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"/> <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"/>

View File

@ -24,16 +24,18 @@ public partial class AssistantRewriteImprove : AssistantBaseCore
protected override bool ShowResult => false; protected override bool ShowResult => false;
#region Overrides of AssistantBase
protected override bool ShowDedicatedProgress => true; protected override bool ShowDedicatedProgress => true;
#endregion protected override IReadOnlyList<IButtonData> FooterButtons =>
[
protected override IReadOnlyList<ButtonData> FooterButtons => new[]
{
new ButtonData("Copy result", Icons.Material.Filled.ContentCopy, Color.Default, string.Empty, () => this.CopyToClipboard(this.rewrittenText)), new ButtonData("Copy result", Icons.Material.Filled.ContentCopy, Color.Default, string.Empty, () => this.CopyToClipboard(this.rewrittenText)),
}; new SendToButton
{
Self = SendToAssistant.REWRITE_ASSISTANT,
UseResultingContentBlockData = false,
GetData = () => string.IsNullOrWhiteSpace(this.rewrittenText) ? this.inputText : this.rewrittenText,
},
];
#region Overrides of ComponentBase #region Overrides of ComponentBase
@ -47,6 +49,10 @@ public partial class AssistantRewriteImprove : AssistantBaseCore
this.selectedWritingStyle = this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedWritingStyle; this.selectedWritingStyle = this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedWritingStyle;
} }
var deferredContent = MessageBus.INSTANCE.CheckDeferredMessages<string>(Event.SEND_TO_REWRITE_ASSISTANT).FirstOrDefault();
if (deferredContent is not null)
this.inputText = deferredContent;
await base.OnInitializedAsync(); await base.OnInitializedAsync();
} }

View File

@ -0,0 +1,14 @@
namespace AIStudio.Components.Pages;
public enum SendToAssistant
{
NONE = 0,
GRAMMAR_SPELLING_ASSISTANT,
ICON_FINDER_ASSISTANT,
REWRITE_ASSISTANT,
TRANSLATION_ASSISTANT,
AGENDA_ASSISTANT,
CODING_ASSISTANT,
TEXT_SUMMARIZER_ASSISTANT,
}

View File

@ -0,0 +1,20 @@
namespace AIStudio.Components.Pages;
public static class SendToAssistantExtensions
{
public static string Name(this SendToAssistant assistant)
{
return assistant switch
{
SendToAssistant.GRAMMAR_SPELLING_ASSISTANT => "Grammar & Spelling Assistant",
SendToAssistant.TEXT_SUMMARIZER_ASSISTANT => "Text Summarizer Assistant",
SendToAssistant.ICON_FINDER_ASSISTANT => "Icon Finder Assistant",
SendToAssistant.TRANSLATION_ASSISTANT => "Translation Assistant",
SendToAssistant.REWRITE_ASSISTANT => "Rewrite Assistant",
SendToAssistant.AGENDA_ASSISTANT => "Agenda Assistant",
SendToAssistant.CODING_ASSISTANT => "Coding Assistant",
_ => "Send to ...",
};
}
}

View File

@ -1,4 +1,4 @@
@page "/settings" @attribute [Route(Path.SETTINGS)]
@using AIStudio.Components.Pages.Coding @using AIStudio.Components.Pages.Coding
@using AIStudio.Components.Pages.TextSummarizer @using AIStudio.Components.Pages.TextSummarizer
@using AIStudio.Provider @using AIStudio.Provider

View File

@ -1,4 +1,4 @@
@page "/Supporters" @attribute [Route(Path.SUPPORTERS)]
<MudText Typo="Typo.h3" Class="mb-2">Supporters</MudText> <MudText Typo="Typo.h3" Class="mb-2">Supporters</MudText>

View File

@ -1,4 +1,4 @@
@page "/assistant/summarizer" @attribute [Route(Path.ASSISTANT_SUMMARIZER)]
@using AIStudio.Tools @using AIStudio.Tools
@inherits AssistantBaseCore @inherits AssistantBaseCore

View File

@ -23,6 +23,14 @@ public partial class AssistantTextSummarizer : AssistantBaseCore
a summary with the requested complexity. In any case, do not add any information. a summary with the requested complexity. In any case, do not add any information.
"""; """;
protected override IReadOnlyList<IButtonData> FooterButtons =>
[
new SendToButton
{
Self = SendToAssistant.TEXT_SUMMARIZER_ASSISTANT,
},
];
private string inputText = string.Empty; private string inputText = string.Empty;
private bool isAgentRunning; private bool isAgentRunning;
private CommonLanguages selectedTargetLanguage; private CommonLanguages selectedTargetLanguage;
@ -43,6 +51,10 @@ public partial class AssistantTextSummarizer : AssistantBaseCore
this.providerSettings = this.SettingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedProvider); this.providerSettings = this.SettingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedProvider);
} }
var deferredContent = MessageBus.INSTANCE.CheckDeferredMessages<string>(Event.SEND_TO_TEXT_SUMMARIZER_ASSISTANT).FirstOrDefault();
if (deferredContent is not null)
this.inputText = deferredContent;
await base.OnInitializedAsync(); await base.OnInitializedAsync();
} }

View File

@ -1,4 +1,4 @@
@page "/assistant/translation" @attribute [Route(Path.ASSISTANT_TRANSLATION)]
@using AIStudio.Tools @using AIStudio.Tools
@inherits AssistantBaseCore @inherits AssistantBaseCore

View File

@ -19,6 +19,14 @@ public partial class AssistantTranslation : AssistantBaseCore
language requires, e.g., shorter sentences, you should split the text into shorter sentences. language requires, e.g., shorter sentences, you should split the text into shorter sentences.
"""; """;
protected override IReadOnlyList<IButtonData> FooterButtons =>
[
new SendToButton
{
Self = SendToAssistant.TRANSLATION_ASSISTANT,
},
];
private bool liveTranslation; private bool liveTranslation;
private bool isAgentRunning; private bool isAgentRunning;
private string inputText = string.Empty; private string inputText = string.Empty;
@ -38,6 +46,10 @@ public partial class AssistantTranslation : AssistantBaseCore
this.providerSettings = this.SettingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == this.SettingsManager.ConfigurationData.Translation.PreselectedProvider); this.providerSettings = this.SettingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == this.SettingsManager.ConfigurationData.Translation.PreselectedProvider);
} }
var deferredContent = MessageBus.INSTANCE.CheckDeferredMessages<string>(Event.SEND_TO_TRANSLATION_ASSISTANT).FirstOrDefault();
if (deferredContent is not null)
this.inputText = deferredContent;
await base.OnInitializedAsync(); await base.OnInitializedAsync();
} }

View File

@ -1,3 +1,6 @@
namespace AIStudio.Tools; namespace AIStudio.Tools;
public readonly record struct ButtonData(string Text, string Icon, Color Color, string Tooltip, Func<Task> AsyncAction); public readonly record struct ButtonData(string Text, string Icon, Color Color, string Tooltip, Func<Task> AsyncAction) : IButtonData
{
public ButtonTypes Type => ButtonTypes.BUTTON;
}

View File

@ -0,0 +1,7 @@
namespace AIStudio.Tools;
public enum ButtonTypes
{
BUTTON = 0,
SEND_TO,
}

View File

@ -15,4 +15,13 @@ public enum Event
// Chat events: // Chat events:
HAS_CHAT_UNSAVED_CHANGES, HAS_CHAT_UNSAVED_CHANGES,
RESET_CHAT_STATE, RESET_CHAT_STATE,
// Send assistant events:
SEND_TO_GRAMMAR_SPELLING_ASSISTANT,
SEND_TO_ICON_FINDER_ASSISTANT,
SEND_TO_REWRITE_ASSISTANT,
SEND_TO_TRANSLATION_ASSISTANT,
SEND_TO_AGENDA_ASSISTANT,
SEND_TO_CODING_ASSISTANT,
SEND_TO_TEXT_SUMMARIZER_ASSISTANT,
} }

View File

@ -0,0 +1,6 @@
namespace AIStudio.Tools;
public interface IButtonData
{
public ButtonTypes Type { get; }
}

View File

@ -11,6 +11,7 @@ public sealed class MessageBus
private readonly ConcurrentDictionary<IMessageBusReceiver, ComponentBase[]> componentFilters = new(); private readonly ConcurrentDictionary<IMessageBusReceiver, ComponentBase[]> componentFilters = new();
private readonly ConcurrentDictionary<IMessageBusReceiver, Event[]> componentEvents = new(); private readonly ConcurrentDictionary<IMessageBusReceiver, Event[]> componentEvents = new();
private readonly ConcurrentDictionary<Event, ConcurrentQueue<Message>> deferredMessages = new();
private readonly ConcurrentQueue<Message> messageQueue = new(); private readonly ConcurrentQueue<Message> messageQueue = new();
private readonly SemaphoreSlim sendingSemaphore = new(1, 1); private readonly SemaphoreSlim sendingSemaphore = new(1, 1);
@ -65,6 +66,24 @@ public sealed class MessageBus
} }
} }
public void DeferMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data = default)
{
if (this.deferredMessages.TryGetValue(triggeredEvent, out var queue))
queue.Enqueue(new Message(sendingComponent, triggeredEvent, data));
else
{
this.deferredMessages[triggeredEvent] = new();
this.deferredMessages[triggeredEvent].Enqueue(new Message(sendingComponent, triggeredEvent, data));
}
}
public IEnumerable<T?> CheckDeferredMessages<T>(Event triggeredEvent)
{
if (this.deferredMessages.TryGetValue(triggeredEvent, out var queue))
while (queue.TryDequeue(out var message))
yield return message.Data is T data ? data : default;
}
public async Task<TResult?> SendMessageUseFirstResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data = default) public async Task<TResult?> SendMessageUseFirstResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data = default)
{ {
foreach (var (receiver, componentFilter) in this.componentFilters) foreach (var (receiver, componentFilter) in this.componentFilters)

View File

@ -0,0 +1,15 @@
using AIStudio.Components.Pages;
namespace AIStudio.Tools;
public readonly record struct SendToButton() : IButtonData
{
public ButtonTypes Type => ButtonTypes.SEND_TO;
public Func<string> GetData { get; init; } = () => string.Empty;
public bool UseResultingContentBlockData { get; init; } = true;
public SendToAssistant Self { get; init; } = SendToAssistant.NONE;
}

View File

@ -0,0 +1,5 @@
# v0.8.9, build 171
- Added the possibility to send an assistant's result to another assistant
- Refactored page paths
- Refactored assistant footer buttons
- Refactored message bus to support deferred messages