mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2025-04-28 11:39:48 +00:00
Refactor basic assistant concepts into a base component
This commit is contained in:
parent
85e429b9ee
commit
f5cb574be8
39
app/MindWork AI Studio/Components/AssistantBase.razor
Normal file
39
app/MindWork AI Studio/Components/AssistantBase.razor
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
@using AIStudio.Chat
|
||||||
|
<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>
|
||||||
|
|
||||||
|
@if (this.inputIssues.Any())
|
||||||
|
{
|
||||||
|
<MudPaper Class="pr-2 mt-3" Outlined="@true">
|
||||||
|
<MudText Typo="Typo.h6">Issues</MudText>
|
||||||
|
<MudList Clickable="@true">
|
||||||
|
@foreach (var issue in this.inputIssues)
|
||||||
|
{
|
||||||
|
<MudListItem Icon="@Icons.Material.Filled.Error" IconColor="Color.Error">
|
||||||
|
@issue
|
||||||
|
</MudListItem>
|
||||||
|
}
|
||||||
|
</MudList>
|
||||||
|
</MudPaper>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (this.resultingContentBlock is not null)
|
||||||
|
{
|
||||||
|
<ContentBlockComponent Role="@this.resultingContentBlock.Role" Type="@this.resultingContentBlock.ContentType" Time="@this.resultingContentBlock.Time" Content="@this.resultingContentBlock.Content" Class="mr-2"/>
|
||||||
|
}
|
||||||
|
</ChildContent>
|
||||||
|
</InnerScrolling>
|
104
app/MindWork AI Studio/Components/AssistantBase.razor.cs
Normal file
104
app/MindWork AI Studio/Components/AssistantBase.razor.cs
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
using AIStudio.Chat;
|
||||||
|
using AIStudio.Provider;
|
||||||
|
using AIStudio.Settings;
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
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 Random RNG { get; set; } = null!;
|
||||||
|
|
||||||
|
protected string Title { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
protected string Description { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
protected abstract string SystemPrompt { get; }
|
||||||
|
|
||||||
|
private protected virtual RenderFragment? Body => null;
|
||||||
|
|
||||||
|
protected AIStudio.Settings.Provider selectedProvider;
|
||||||
|
protected MudForm? form;
|
||||||
|
protected bool inputIsValid;
|
||||||
|
|
||||||
|
private ChatThread? chatThread;
|
||||||
|
private ContentBlock? resultingContentBlock;
|
||||||
|
private string[] inputIssues = [];
|
||||||
|
|
||||||
|
#region Overrides of ComponentBase
|
||||||
|
|
||||||
|
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 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 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);
|
||||||
|
|
||||||
|
// Use the selected provider to get the AI response.
|
||||||
|
// By awaiting this line, we wait for the entire
|
||||||
|
// content to be streamed.
|
||||||
|
await aiText.CreateFromProviderAsync(this.selectedProvider.UsedProvider.CreateProvider(this.selectedProvider.InstanceName, this.selectedProvider.Hostname), this.JsRuntime, this.SettingsManager, this.selectedProvider.Model, this.chatThread);
|
||||||
|
}
|
||||||
|
}
|
19
app/MindWork AI Studio/Components/AssistantBaseCore.cs
Normal file
19
app/MindWork AI Studio/Components/AssistantBaseCore.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.AspNetCore.Components.Rendering;
|
||||||
|
|
||||||
|
namespace AIStudio.Components;
|
||||||
|
|
||||||
|
//
|
||||||
|
// See https://stackoverflow.com/a/77300384/2258393 for why this class is needed
|
||||||
|
//
|
||||||
|
|
||||||
|
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,68 +1,29 @@
|
|||||||
@page "/assistant/icons"
|
@page "/assistant/icons"
|
||||||
@using AIStudio.Chat
|
|
||||||
@using AIStudio.Settings
|
@using AIStudio.Settings
|
||||||
|
@inherits AssistantBaseCore
|
||||||
|
|
||||||
<MudText Typo="Typo.h3" Class="mb-2 mr-3">
|
<MudTextField T="string" @bind-Text="@this.inputContext" Validation="@this.ValidatingContext" AdornmentIcon="@Icons.Material.Filled.TextFields" Adornment="Adornment.Start" Label="Your context" Variant="Variant.Outlined" Lines="3" AutoGrow="@true" MaxLines="12" Class="mb-3"/>
|
||||||
Icon Finder
|
|
||||||
</MudText>
|
|
||||||
|
|
||||||
<InnerScrolling HeaderHeight="12.3em">
|
<MudStack Row="@true" AlignItems="AlignItems.Center" Class="mb-3">
|
||||||
<ChildContent>
|
<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">
|
||||||
<MudForm @ref="@this.form" @bind-IsValid="@this.inputIsValid" @bind-Errors="@this.inputIssues" Class="pr-2">
|
@foreach (var source in Enum.GetValues<IconSources>())
|
||||||
<MudText Typo="Typo.body1" Align="Align.Justify" Class="mb-6">
|
|
||||||
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.
|
|
||||||
</MudText>
|
|
||||||
|
|
||||||
<MudTextField T="string" @bind-Text="@this.inputContext" Validation="@this.ValidatingContext" AdornmentIcon="@Icons.Material.Filled.TextFields" Adornment="Adornment.Start" Label="Yout context" Variant="Variant.Outlined" Lines="3" AutoGrow="@true" MaxLines="12" Class="mb-3"/>
|
|
||||||
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<MudSelect T="Provider" @bind-Value="@this.selectedProvider" Validation="@this.ValidatingProvider" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Apps" Margin="Margin.Dense" Label="Provider" Class="mb-3 rounded-lg" Variant="Variant.Outlined">
|
|
||||||
@foreach (var provider in this.SettingsManager.ConfigurationData.Providers)
|
|
||||||
{
|
|
||||||
<MudSelectItem Value="@provider"/>
|
|
||||||
}
|
|
||||||
</MudSelect>
|
|
||||||
|
|
||||||
<MudButton Variant="Variant.Filled" Class="mb-3" OnClick="() => this.FindIcon()">
|
|
||||||
Find icons
|
|
||||||
</MudButton>
|
|
||||||
</MudForm>
|
|
||||||
|
|
||||||
@if (this.inputIssues.Any())
|
|
||||||
{
|
{
|
||||||
<MudPaper Class="pr-2 mt-3" Outlined="@true">
|
<MudSelectItem Value="@source">@source.Name()</MudSelectItem>
|
||||||
<MudText Typo="Typo.h6">Issues</MudText>
|
|
||||||
<MudList Clickable="@true">
|
|
||||||
@foreach (var issue in this.inputIssues)
|
|
||||||
{
|
|
||||||
<MudListItem Icon="@Icons.Material.Filled.Error" IconColor="Color.Error">
|
|
||||||
@issue
|
|
||||||
</MudListItem>
|
|
||||||
}
|
|
||||||
</MudList>
|
|
||||||
</MudPaper>
|
|
||||||
}
|
}
|
||||||
|
</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>
|
||||||
|
|
||||||
@if (this.resultingContentBlock is not null)
|
<MudSelect T="Provider" @bind-Value="@this.selectedProvider" Validation="@this.ValidatingProvider" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Apps" Margin="Margin.Dense" Label="Provider" Class="mb-3 rounded-lg" Variant="Variant.Outlined">
|
||||||
{
|
@foreach (var provider in this.SettingsManager.ConfigurationData.Providers)
|
||||||
<ContentBlockComponent Role="@this.resultingContentBlock.Role" Type="@this.resultingContentBlock.ContentType" Time="@this.resultingContentBlock.Time" Content="@this.resultingContentBlock.Content" Class="mr-2"/>
|
{
|
||||||
}
|
<MudSelectItem Value="@provider"/>
|
||||||
</ChildContent>
|
}
|
||||||
</InnerScrolling>
|
</MudSelect>
|
||||||
|
|
||||||
|
<MudButton Variant="Variant.Filled" Class="mb-3" OnClick="() => this.FindIcon()">
|
||||||
|
Find icon
|
||||||
|
</MudButton>
|
@ -1,45 +1,26 @@
|
|||||||
using AIStudio.Chat;
|
|
||||||
using AIStudio.Provider;
|
using AIStudio.Provider;
|
||||||
using AIStudio.Settings;
|
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Components;
|
|
||||||
|
|
||||||
namespace AIStudio.Components.Pages.IconFinder;
|
namespace AIStudio.Components.Pages.IconFinder;
|
||||||
|
|
||||||
public partial class AssistantIconFinder : ComponentBase
|
public partial class AssistantIconFinder : AssistantBaseCore
|
||||||
{
|
{
|
||||||
[Inject]
|
|
||||||
private SettingsManager SettingsManager { get; set; } = null!;
|
|
||||||
|
|
||||||
[Inject]
|
|
||||||
public IJSRuntime JsRuntime { get; init; } = null!;
|
|
||||||
|
|
||||||
[Inject]
|
|
||||||
public Random RNG { get; set; } = null!;
|
|
||||||
|
|
||||||
private ChatThread? chatThread;
|
|
||||||
private ContentBlock? resultingContentBlock;
|
|
||||||
private AIStudio.Settings.Provider selectedProvider;
|
|
||||||
private MudForm form = null!;
|
|
||||||
private bool inputIsValid;
|
|
||||||
private string[] inputIssues = [];
|
|
||||||
private string inputContext = string.Empty;
|
private string inputContext = string.Empty;
|
||||||
private IconSources selectedIconSource;
|
private IconSources selectedIconSource;
|
||||||
|
|
||||||
#region Overrides of ComponentBase
|
public AssistantIconFinder()
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
||||||
{
|
{
|
||||||
// Reset the validation when not editing and on the first render.
|
this.Title = "Icon Finder";
|
||||||
// We don't want to show validation errors when the user opens the dialog.
|
this.Description =
|
||||||
if(firstRender)
|
"""
|
||||||
this.form.ResetValidation();
|
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
|
||||||
await base.OnAfterRenderAsync(firstRender);
|
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.
|
||||||
|
""";
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
private string? ValidatingContext(string context)
|
private string? ValidatingContext(string context)
|
||||||
{
|
{
|
||||||
if(string.IsNullOrWhiteSpace(context))
|
if(string.IsNullOrWhiteSpace(context))
|
||||||
@ -58,72 +39,24 @@ public partial class AssistantIconFinder : ComponentBase
|
|||||||
|
|
||||||
private async Task FindIcon()
|
private async Task FindIcon()
|
||||||
{
|
{
|
||||||
await this.form.Validate();
|
await this.form!.Validate();
|
||||||
if (!this.inputIsValid)
|
if (!this.inputIsValid)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
//
|
this.CreateChatThread();
|
||||||
// Create a new chat thread:
|
var time = this.AddUserRequest(
|
||||||
//
|
$"""
|
||||||
this.chatThread = new()
|
{this.selectedIconSource.Prompt()} I search for an icon for the following context:
|
||||||
{
|
|
||||||
WorkspaceId = Guid.Empty,
|
```
|
||||||
ChatId = Guid.NewGuid(),
|
{this.inputContext}
|
||||||
Name = string.Empty,
|
```
|
||||||
Seed = this.RNG.Next(),
|
""");
|
||||||
SystemPrompt = SYSTEM_PROMPT,
|
|
||||||
Blocks = [],
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Add the user's request 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.selectedIconSource.Prompt()} I search for an icon for the following context:
|
|
||||||
|
|
||||||
```
|
|
||||||
{this.inputContext}
|
|
||||||
```
|
|
||||||
""",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
//
|
|
||||||
// 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.resultingContentBlock = new ContentBlock
|
await this.AddAIResponseAsync(time);
|
||||||
{
|
|
||||||
Time = time,
|
|
||||||
ContentType = ContentType.TEXT,
|
|
||||||
Role = ChatRole.AI,
|
|
||||||
Content = aiText,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.chatThread?.Blocks.Add(this.resultingContentBlock);
|
|
||||||
|
|
||||||
// Use the selected provider to get the AI response.
|
|
||||||
// By awaiting this line, we wait for the entire
|
|
||||||
// content to be streamed.
|
|
||||||
await aiText.CreateFromProviderAsync(this.selectedProvider.UsedProvider.CreateProvider(this.selectedProvider.InstanceName, this.selectedProvider.Hostname), this.JsRuntime, this.SettingsManager, this.selectedProvider.Model, this.chatThread);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private const string SYSTEM_PROMPT =
|
protected override string SystemPrompt =>
|
||||||
"""
|
"""
|
||||||
I can search for icons using US English keywords. Please help me come up with the right search queries.
|
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
|
I don't want you to translate my requests word-for-word into US English. Instead, you should provide keywords
|
||||||
|
Loading…
Reference in New Issue
Block a user