mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-08 07:09:40 +00:00
Added the writer mode experiment (#167)
This commit is contained in:
1 parent
432ae30fdf
commit
0a951ead3e
6 files changed
+239
-3
No files matched your search
@@ -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
|
||||
}
|
||||
Reference in new issue
Block a user