mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2025-04-28 07: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;
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
namespace AIStudio.Components;
|
namespace AIStudio.Components;
|
||||||
|
|
||||||
public abstract class MSGComponentBase : ComponentBase, IDisposable, IMessageBusReceiver
|
public abstract class MSGComponentBase : ComponentBase, IDisposable, IMessageBusReceiver
|
||||||
{
|
{
|
||||||
|
[Inject]
|
||||||
|
protected SettingsManager SettingsManager { get; init; } = null!;
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
protected MessageBus MessageBus { get; init; } = null!;
|
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("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("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("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("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("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),
|
new("Settings", Icons.Material.Filled.Settings, palette.DarkLighten, palette.GrayLight, Routes.SETTINGS, false),
|
||||||
|
@ -17,9 +17,6 @@ namespace AIStudio.Pages;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class Chat : MSGComponentBase, IAsyncDisposable
|
public partial class Chat : MSGComponentBase, IAsyncDisposable
|
||||||
{
|
{
|
||||||
[Inject]
|
|
||||||
private SettingsManager SettingsManager { get; init; } = null!;
|
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
private ThreadSafeRandom RNG { get; init; } = null!;
|
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 ASSISTANTS = "/assistants";
|
||||||
public const string SETTINGS = "/settings";
|
public const string SETTINGS = "/settings";
|
||||||
public const string SUPPORTERS = "/supporters";
|
public const string SUPPORTERS = "/supporters";
|
||||||
|
public const string WRITER = "/writer";
|
||||||
|
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
public const string ASSISTANT_TRANSLATION = "/assistant/translation";
|
public const string ASSISTANT_TRANSLATION = "/assistant/translation";
|
||||||
|
Loading…
Reference in New Issue
Block a user