mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2025-02-05 21:29:06 +00:00
177 lines
5.4 KiB
C#
177 lines
5.4 KiB
C#
|
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
|
||
|
}
|