mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2025-04-27 15:59:47 +00:00
Added the writer mode experiment (#167)
This commit is contained in:
parent
432ae30fdf
commit
0a951ead3e
@ -1,9 +1,14 @@
|
||||
using AIStudio.Settings;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Components;
|
||||
|
||||
public abstract class MSGComponentBase : ComponentBase, IDisposable, IMessageBusReceiver
|
||||
{
|
||||
[Inject]
|
||||
protected SettingsManager SettingsManager { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
protected MessageBus MessageBus { get; init; } = null!;
|
||||
|
||||
|
@ -103,6 +103,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDis
|
||||
new("Home", Icons.Material.Filled.Home, palette.DarkLighten, palette.GrayLight, Routes.HOME, true),
|
||||
new("Chat", Icons.Material.Filled.Chat, palette.DarkLighten, palette.GrayLight, Routes.CHAT, false),
|
||||
new("Assistants", Icons.Material.Filled.Apps, palette.DarkLighten, palette.GrayLight, Routes.ASSISTANTS, false),
|
||||
new("Writer", Icons.Material.Filled.Create, palette.DarkLighten, palette.GrayLight, Routes.WRITER, false),
|
||||
new("Supporters", Icons.Material.Filled.Favorite, palette.Error.Value, "#801a00", Routes.SUPPORTERS, false),
|
||||
new("About", Icons.Material.Filled.Info, palette.DarkLighten, palette.GrayLight, Routes.ABOUT, false),
|
||||
new("Settings", Icons.Material.Filled.Settings, palette.DarkLighten, palette.GrayLight, Routes.SETTINGS, false),
|
||||
|
@ -17,9 +17,6 @@ namespace AIStudio.Pages;
|
||||
/// </summary>
|
||||
public partial class Chat : MSGComponentBase, IAsyncDisposable
|
||||
{
|
||||
[Inject]
|
||||
private SettingsManager SettingsManager { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private ThreadSafeRandom RNG { get; init; } = null!;
|
||||
|
||||
|
55
app/MindWork AI Studio/Pages/Writer.razor
Normal file
55
app/MindWork AI Studio/Pages/Writer.razor
Normal file
@ -0,0 +1,55 @@
|
||||
@attribute [Route(Routes.WRITER)]
|
||||
@inherits MSGComponentBase
|
||||
|
||||
<MudText Typo="Typo.h3" Class="mb-2 mr-3">
|
||||
Writer
|
||||
</MudText>
|
||||
|
||||
<ProviderSelection @bind-ProviderSettings="@this.providerSettings"/>
|
||||
<InnerScrolling HeaderHeight="12.3em">
|
||||
<ChildContent>
|
||||
<MudTextField
|
||||
@ref="@this.textField"
|
||||
T="string"
|
||||
Label="Write your text"
|
||||
@bind-Text="@this.userInput"
|
||||
Immediate="@true"
|
||||
Lines="16"
|
||||
MaxLines="16"
|
||||
Typo="Typo.body1"
|
||||
Variant="Variant.Outlined"
|
||||
InputMode="InputMode.text"
|
||||
FullWidth="@true"
|
||||
OnKeyDown="@this.InputKeyEvent"
|
||||
UserAttributes="@USER_INPUT_ATTRIBUTES"/>
|
||||
|
||||
<MudTextField
|
||||
T="string"
|
||||
Label="Your stage directions"
|
||||
@bind-Text="@this.userDirection"
|
||||
Immediate="@true"
|
||||
Lines="4"
|
||||
MaxLines="4"
|
||||
Typo="Typo.body1"
|
||||
Variant="Variant.Outlined"
|
||||
InputMode="InputMode.text"
|
||||
FullWidth="@true"
|
||||
UserAttributes="@USER_INPUT_ATTRIBUTES"/>
|
||||
</ChildContent>
|
||||
<FooterContent>
|
||||
@if (this.isStreaming)
|
||||
{
|
||||
<MudProgressLinear Color="Color.Primary" Indeterminate="true" Class="mb-6" />
|
||||
}
|
||||
<MudTextField
|
||||
T="string"
|
||||
Label="Suggestion"
|
||||
@bind-Text="@this.suggestion"
|
||||
ReadOnly="@true"
|
||||
Lines="3"
|
||||
Typo="Typo.body1"
|
||||
Variant="Variant.Outlined"
|
||||
FullWidth="@true"
|
||||
UserAttributes="@USER_INPUT_ATTRIBUTES"/>
|
||||
</FooterContent>
|
||||
</InnerScrolling>
|
177
app/MindWork AI Studio/Pages/Writer.razor.cs
Normal file
177
app/MindWork AI Studio/Pages/Writer.razor.cs
Normal file
@ -0,0 +1,177 @@
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Components;
|
||||
using AIStudio.Provider;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
|
||||
using Timer = System.Timers.Timer;
|
||||
|
||||
namespace AIStudio.Pages;
|
||||
|
||||
public partial class Writer : MSGComponentBase, IAsyncDisposable
|
||||
{
|
||||
[Inject]
|
||||
private ILogger<Chat> Logger { get; init; } = null!;
|
||||
|
||||
private static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new();
|
||||
private readonly Timer typeTimer = new(TimeSpan.FromMilliseconds(1_500));
|
||||
|
||||
private MudTextField<string> textField = null!;
|
||||
private AIStudio.Settings.Provider providerSettings;
|
||||
private ChatThread? chatThread;
|
||||
private bool isStreaming;
|
||||
private string userInput = string.Empty;
|
||||
private string userDirection = string.Empty;
|
||||
private string suggestion = string.Empty;
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
this.ApplyFilters([], []);
|
||||
this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES);
|
||||
this.typeTimer.Elapsed += async (_, _) => await this.InvokeAsync(this.GetSuggestions);
|
||||
this.typeTimer.AutoReset = false;
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Overrides of MSGComponentBase
|
||||
|
||||
public override Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) where TResult : default where TPayload : default
|
||||
{
|
||||
return Task.FromResult(default(TResult));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private bool IsProviderSelected => this.providerSettings.UsedLLMProvider != LLMProviders.NONE;
|
||||
|
||||
private async Task InputKeyEvent(KeyboardEventArgs keyEvent)
|
||||
{
|
||||
var key = keyEvent.Code.ToLowerInvariant();
|
||||
var isTab = key is "tab";
|
||||
var isModifier = keyEvent.AltKey || keyEvent.CtrlKey || keyEvent.MetaKey || keyEvent.ShiftKey;
|
||||
|
||||
if (isTab && !isModifier)
|
||||
{
|
||||
await this.textField.FocusAsync();
|
||||
this.AcceptNextWord();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isTab && isModifier)
|
||||
{
|
||||
await this.textField.FocusAsync();
|
||||
this.AcceptEntireSuggestion();
|
||||
return;
|
||||
}
|
||||
|
||||
if(!isModifier)
|
||||
{
|
||||
this.typeTimer.Stop();
|
||||
this.typeTimer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task GetSuggestions()
|
||||
{
|
||||
if (!this.IsProviderSelected)
|
||||
return;
|
||||
|
||||
this.chatThread ??= new()
|
||||
{
|
||||
WorkspaceId = Guid.Empty,
|
||||
ChatId = Guid.NewGuid(),
|
||||
Name = string.Empty,
|
||||
Seed = 798798,
|
||||
SystemPrompt = """
|
||||
You are an assistant who helps with writing documents. You receive a sample
|
||||
from a document as input. As output, you provide how the begun sentence could
|
||||
continue. You give exactly one variant, not multiple. If the current sentence
|
||||
is complete, you provide an empty response. You do not ask questions, and you
|
||||
do not repeat the task.
|
||||
""",
|
||||
Blocks = [],
|
||||
};
|
||||
|
||||
var time = DateTimeOffset.Now;
|
||||
this.chatThread.Blocks.Clear();
|
||||
this.chatThread.Blocks.Add(new ContentBlock
|
||||
{
|
||||
Time = time,
|
||||
ContentType = ContentType.TEXT,
|
||||
Role = ChatRole.USER,
|
||||
Content = new ContentText
|
||||
{
|
||||
// We use the maximum 160 characters from the end of the text:
|
||||
Text = this.userInput.Length > 160 ? this.userInput[^160..] : this.userInput,
|
||||
},
|
||||
});
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
this.isStreaming = true;
|
||||
this.StateHasChanged();
|
||||
|
||||
await aiText.CreateFromProviderAsync(this.providerSettings.CreateProvider(this.Logger), this.SettingsManager, this.providerSettings.Model, this.chatThread);
|
||||
this.suggestion = aiText.Text;
|
||||
|
||||
this.isStreaming = false;
|
||||
this.StateHasChanged();
|
||||
}
|
||||
|
||||
private void AcceptEntireSuggestion()
|
||||
{
|
||||
if(this.userInput.Last() != ' ')
|
||||
this.userInput += ' ';
|
||||
|
||||
this.userInput += this.suggestion;
|
||||
this.suggestion = string.Empty;
|
||||
this.StateHasChanged();
|
||||
}
|
||||
|
||||
private void AcceptNextWord()
|
||||
{
|
||||
var words = this.suggestion.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
if(words.Length == 0)
|
||||
return;
|
||||
|
||||
if(this.userInput.Last() != ' ')
|
||||
this.userInput += ' ';
|
||||
|
||||
this.userInput += words[0] + ' ';
|
||||
this.suggestion = string.Join(' ', words.Skip(1));
|
||||
this.StateHasChanged();
|
||||
}
|
||||
|
||||
#region Implementation of IAsyncDisposable
|
||||
|
||||
public ValueTask DisposeAsync()
|
||||
{
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
@ -8,6 +8,7 @@ public sealed partial class Routes
|
||||
public const string ASSISTANTS = "/assistants";
|
||||
public const string SETTINGS = "/settings";
|
||||
public const string SUPPORTERS = "/supporters";
|
||||
public const string WRITER = "/writer";
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
public const string ASSISTANT_TRANSLATION = "/assistant/translation";
|
||||
|
Loading…
Reference in New Issue
Block a user