mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2025-02-05 17:09:06 +00:00
Add a grammar and spell checker assistant (#72)
This commit is contained in:
parent
77e7e044e3
commit
05a7e44de4
@ -13,6 +13,7 @@
|
|||||||
<link href="system/MudBlazor.Markdown/MudBlazor.Markdown.min.css" rel="stylesheet" />
|
<link href="system/MudBlazor.Markdown/MudBlazor.Markdown.min.css" rel="stylesheet" />
|
||||||
<link href="app.css" rel="stylesheet" />
|
<link href="app.css" rel="stylesheet" />
|
||||||
<HeadOutlet/>
|
<HeadOutlet/>
|
||||||
|
<script src="diff.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body style="overflow: hidden;">
|
<body style="overflow: hidden;">
|
||||||
|
@ -17,9 +17,43 @@
|
|||||||
</MudForm>
|
</MudForm>
|
||||||
<Issues IssuesData="@this.inputIssues"/>
|
<Issues IssuesData="@this.inputIssues"/>
|
||||||
|
|
||||||
@if (this.resultingContentBlock is not null)
|
@if (this.isProcessing)
|
||||||
{
|
{
|
||||||
<ContentBlockComponent Role="@this.resultingContentBlock.Role" Type="@this.resultingContentBlock.ContentType" Time="@this.resultingContentBlock.Time" Content="@this.resultingContentBlock.Content" Class="mr-2"/>
|
<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 buttonData in this.FooterButtons)
|
||||||
|
{
|
||||||
|
switch (buttonData)
|
||||||
|
{
|
||||||
|
case var _ 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;
|
||||||
|
|
||||||
|
default:
|
||||||
|
<MudButton Variant="Variant.Filled" Color="@buttonData.Color" StartIcon="@GetButtonIcon(buttonData.Icon)" OnClick="async () => await buttonData.AsyncAction()">
|
||||||
|
@buttonData.Text
|
||||||
|
</MudButton>
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
}
|
}
|
||||||
</ChildContent>
|
</ChildContent>
|
||||||
</InnerScrolling>
|
</InnerScrolling>
|
@ -18,6 +18,15 @@ public abstract partial class AssistantBase : ComponentBase
|
|||||||
[Inject]
|
[Inject]
|
||||||
protected ThreadSafeRandom RNG { get; init; } = null!;
|
protected ThreadSafeRandom RNG { get; init; } = null!;
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected ISnackbar Snackbar { get; init; } = null!;
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected Rust Rust { 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 Title { get; }
|
||||||
|
|
||||||
protected abstract string Description { get; }
|
protected abstract string Description { get; }
|
||||||
@ -26,6 +35,10 @@ public abstract partial class AssistantBase : ComponentBase
|
|||||||
|
|
||||||
private protected virtual RenderFragment? Body => null;
|
private protected virtual RenderFragment? Body => null;
|
||||||
|
|
||||||
|
protected virtual bool ShowResult => true;
|
||||||
|
|
||||||
|
protected virtual IReadOnlyList<ButtonData> FooterButtons => [];
|
||||||
|
|
||||||
protected static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new();
|
protected static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new();
|
||||||
|
|
||||||
protected AIStudio.Settings.Provider providerSettings;
|
protected AIStudio.Settings.Provider providerSettings;
|
||||||
@ -35,6 +48,7 @@ public abstract partial class AssistantBase : ComponentBase
|
|||||||
private ChatThread? chatThread;
|
private ChatThread? chatThread;
|
||||||
private ContentBlock? resultingContentBlock;
|
private ContentBlock? resultingContentBlock;
|
||||||
private string[] inputIssues = [];
|
private string[] inputIssues = [];
|
||||||
|
private bool isProcessing;
|
||||||
|
|
||||||
#region Overrides of ComponentBase
|
#region Overrides of ComponentBase
|
||||||
|
|
||||||
@ -96,7 +110,7 @@ public abstract partial class AssistantBase : ComponentBase
|
|||||||
return time;
|
return time;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async Task AddAIResponseAsync(DateTimeOffset time)
|
protected async Task<string> AddAIResponseAsync(DateTimeOffset time)
|
||||||
{
|
{
|
||||||
var aiText = new ContentText
|
var aiText = new ContentText
|
||||||
{
|
{
|
||||||
@ -114,10 +128,26 @@ public abstract partial class AssistantBase : ComponentBase
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.chatThread?.Blocks.Add(this.resultingContentBlock);
|
this.chatThread?.Blocks.Add(this.resultingContentBlock);
|
||||||
|
this.isProcessing = true;
|
||||||
|
this.StateHasChanged();
|
||||||
|
|
||||||
// Use the selected provider to get the AI response.
|
// Use the selected provider to get the AI response.
|
||||||
// By awaiting this line, we wait for the entire
|
// By awaiting this line, we wait for the entire
|
||||||
// content to be streamed.
|
// content to be streamed.
|
||||||
await aiText.CreateFromProviderAsync(this.providerSettings.CreateProvider(), this.JsRuntime, this.SettingsManager, this.providerSettings.Model, this.chatThread);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? GetButtonIcon(string icon)
|
||||||
|
{
|
||||||
|
if(string.IsNullOrWhiteSpace(icon))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return icon;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Components.Rendering;
|
|||||||
namespace AIStudio.Components;
|
namespace AIStudio.Components;
|
||||||
|
|
||||||
//
|
//
|
||||||
// See https://stackoverflow.com/a/77300384/2258393 for why this class is needed
|
// See https://stackoverflow.com/a/77300384/2258393 for why this class is necessary
|
||||||
//
|
//
|
||||||
|
|
||||||
public abstract class AssistantBaseCore : AssistantBase
|
public abstract class AssistantBaseCore : AssistantBase
|
||||||
|
@ -49,6 +49,7 @@
|
|||||||
<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="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="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="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>
|
</MudGrid>
|
||||||
</ExpansionPanel>
|
</ExpansionPanel>
|
||||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Verified" HeaderText="License: FSL-1.1-MIT">
|
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Verified" HeaderText="License: FSL-1.1-MIT">
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
<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="/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="/assistant/translation"/>
|
||||||
|
<AssistantBlock Name="Grammar & Spelling" Description="Check grammar and spelling of a given text." Icon="@Icons.Material.Filled.Edit" Link="/assistant/grammar-spelling"/>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
|
||||||
<MudText Typo="Typo.h4" Class="mb-2 mr-3 mt-6">
|
<MudText Typo="Typo.h4" Class="mb-2 mr-3 mt-6">
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
@using AIStudio.Tools
|
||||||
|
@page "/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>
|
@ -0,0 +1,84 @@
|
|||||||
|
using AIStudio.Tools;
|
||||||
|
|
||||||
|
namespace AIStudio.Components.Pages.GrammarSpelling;
|
||||||
|
|
||||||
|
public partial class AssistantGrammarSpelling : AssistantBaseCore
|
||||||
|
{
|
||||||
|
protected override string Title => "Grammar and 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 IReadOnlyList<ButtonData> FooterButtons => new[]
|
||||||
|
{
|
||||||
|
new ButtonData("Copy corrected text", Icons.Material.Filled.ContentCopy, Color.Default, string.Empty, this.CopyToClipboard),
|
||||||
|
};
|
||||||
|
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CopyToClipboard()
|
||||||
|
{
|
||||||
|
await this.Rust.CopyText2Clipboard(this.JsRuntime, this.Snackbar, this.correctedText);
|
||||||
|
}
|
||||||
|
}
|
3
app/MindWork AI Studio/Tools/ButtonData.cs
Normal file
3
app/MindWork AI Studio/Tools/ButtonData.cs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
namespace AIStudio.Tools;
|
||||||
|
|
||||||
|
public readonly record struct ButtonData(string Text, string Icon, Color Color, string Tooltip, Func<Task> AsyncAction);
|
@ -42,4 +42,12 @@ public static class CommonLanguageExtensions
|
|||||||
|
|
||||||
return language.Name();
|
return language.Name();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string NameSelectingOptional(this CommonLanguages language)
|
||||||
|
{
|
||||||
|
if(language is CommonLanguages.AS_IS)
|
||||||
|
return "Do not specify the language";
|
||||||
|
|
||||||
|
return language.Name();
|
||||||
|
}
|
||||||
}
|
}
|
11
app/MindWork AI Studio/Tools/JsRuntimeExtensions.cs
Normal file
11
app/MindWork AI Studio/Tools/JsRuntimeExtensions.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
using AIStudio.Components;
|
||||||
|
|
||||||
|
namespace AIStudio.Tools;
|
||||||
|
|
||||||
|
public static class JsRuntimeExtensions
|
||||||
|
{
|
||||||
|
public static async Task GenerateAndShowDiff(this IJSRuntime jsRuntime, string text1, string text2)
|
||||||
|
{
|
||||||
|
await jsRuntime.InvokeVoidAsync("generateDiff", text1, text2, AssistantBase.ASSISTANT_RESULT_DIV_ID, AssistantBase.AFTER_RESULT_DIV_ID);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
window.generateDiff = function (text1, text2, divDiff, divLegend) {
|
||||||
|
let wikEdDiff = new WikEdDiff();
|
||||||
|
let targetDiv = document.getElementById(divDiff)
|
||||||
|
targetDiv.innerHTML = wikEdDiff.diff(text1, text2);
|
||||||
|
targetDiv.classList.add('mud-typography-body1');
|
||||||
|
|
||||||
|
let legend = document.getElementById(divLegend);
|
||||||
|
legend.innerHTML = `
|
||||||
|
<div class="legend mt-2">
|
||||||
|
<h3>Legend</h3>
|
||||||
|
<ul class="mt-2">
|
||||||
|
<li><span class="wikEdDiffMarkRight" title="Moved block" id="wikEdDiffMark999" onmouseover="wikEdDiffBlockHandler(undefined, this, 'mouseover');"></span> Original block position</li>
|
||||||
|
<li><span title="+" class="wikEdDiffInsert">Inserted<span class="wikEdDiffSpace"><span class="wikEdDiffSpaceSymbol"></span> </span>text<span class="wikEdDiffNewline"> </span></span></li>
|
||||||
|
<li><span title="−" class="wikEdDiffDelete">Deleted<span class="wikEdDiffSpace"><span class="wikEdDiffSpaceSymbol"></span> </span>text<span class="wikEdDiffNewline"> </span></span></li>
|
||||||
|
<li><span class="wikEdDiffBlockLeft" title="◀" id="wikEdDiffBlock999" onmouseover="wikEdDiffBlockHandler(undefined, this, 'mouseover');">Moved<span class="wikEdDiffSpace"><span class="wikEdDiffSpaceSymbol"></span> </span>block<span class="wikEdDiffNewline"> </span></span></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
@ -1,2 +1,4 @@
|
|||||||
# v0.8.8, build 170
|
# v0.8.8, build 170
|
||||||
|
- Added a grammar and spell checker assistant
|
||||||
|
- Improved all assistants by showing a progress bar while processing
|
||||||
- Upgraded MudBlazor to v7.6.0
|
- Upgraded MudBlazor to v7.6.0
|
2
app/MindWork AI Studio/wwwroot/diff.js
Normal file
2
app/MindWork AI Studio/wwwroot/diff.js
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user