using AIStudio.Settings; using Microsoft.AspNetCore.Components; using RustService = AIStudio.Tools.RustService; namespace AIStudio.Chat; /// /// The UI component for a chat content block, i.e., for any IContent. /// public partial class ContentBlockComponent : ComponentBase { /// /// The role of the chat content block. /// [Parameter] public ChatRole Role { get; init; } = ChatRole.NONE; /// /// The content. /// [Parameter] public IContent Content { get; init; } = new ContentText(); /// /// The content type. /// [Parameter] public ContentType Type { get; init; } = ContentType.NONE; /// /// When was the content created? /// [Parameter] public DateTimeOffset Time { get; init; } /// /// Optional CSS classes. /// [Parameter] public string Class { get; set; } = string.Empty; [Parameter] public bool IsLastContentBlock { get; set; } = false; [Parameter] public bool IsSecondToLastBlock { get; set; } = false; [Parameter] public Func? RemoveBlockFunc { get; set; } [Parameter] public Func? RegenerateFunc { get; set; } [Parameter] public Func? EditLastBlockFunc { get; set; } [Parameter] public Func? EditLastUserBlockFunc { get; set; } [Parameter] public Func RegenerateEnabled { get; set; } = () => false; [Inject] private RustService RustService { get; init; } = null!; [Inject] private ISnackbar Snackbar { get; init; } = null!; [Inject] private SettingsManager SettingsManager { get; init; } = null!; [Inject] private IDialogService DialogService { get; init; } = null!; private bool HideContent { get; set; } #region Overrides of ComponentBase protected override async Task OnInitializedAsync() { // Register the streaming events: this.Content.StreamingDone = this.AfterStreaming; this.Content.StreamingEvent = () => this.InvokeAsync(this.StateHasChanged); await base.OnInitializedAsync(); } /// /// Gets called when the content stream ended. /// private async Task AfterStreaming() { // Might be called from a different thread, so we need to invoke the UI thread: await this.InvokeAsync(async () => { // // Issue we try to solve: When the content changes during streaming, // Blazor might fail to see all changes made to the render tree. // This happens mostly when Markdown code blocks are streamed. // // Hide the content for a short time: this.HideContent = true; // Let Blazor update the UI, i.e., to see the render tree diff: this.StateHasChanged(); // Show the content again: this.HideContent = false; // Let Blazor update the UI, i.e., to see the render tree diff: this.StateHasChanged(); // Inform the chat that the streaming is done: await MessageBus.INSTANCE.SendMessage(this, Event.CHAT_STREAMING_DONE); }); } #endregion /// /// Copy this block's content to the clipboard. /// private async Task CopyToClipboard() { switch (this.Type) { case ContentType.TEXT: var textContent = (ContentText) this.Content; await this.RustService.CopyText2Clipboard(this.Snackbar, textContent.Text); break; default: this.Snackbar.Add("Cannot copy this content type to clipboard!", Severity.Error, config => { config.Icon = Icons.Material.Filled.ContentCopy; config.IconSize = Size.Large; config.IconColor = Color.Error; }); break; } } private string CardClasses => $"my-2 rounded-lg {this.Class}"; private CodeBlockTheme CodeColorPalette => this.SettingsManager.IsDarkMode ? CodeBlockTheme.Dark : CodeBlockTheme.Default; private async Task RemoveBlock() { if (this.RemoveBlockFunc is null) return; var remove = await this.DialogService.ShowMessageBox( "Remove Message", "Do you really want to remove this message?", "Yes, remove it", "No, keep it"); if (remove.HasValue && remove.Value) await this.RemoveBlockFunc(this.Content); } private async Task RegenerateBlock() { if (this.RegenerateFunc is null) return; if(this.Role is not ChatRole.AI) return; var regenerate = await this.DialogService.ShowMessageBox( "Regenerate Message", "Do you really want to regenerate this message?", "Yes, regenerate it", "No, keep it"); if (regenerate.HasValue && regenerate.Value) await this.RegenerateFunc(this.Content); } private async Task EditLastBlock() { if (this.EditLastBlockFunc is null) return; if(this.Role is not ChatRole.USER) return; await this.EditLastBlockFunc(this.Content); } private async Task EditLastUserBlock() { if (this.EditLastUserBlockFunc is null) return; if(this.Role is not ChatRole.USER) return; var edit = await this.DialogService.ShowMessageBox( "Edit Message", "Do you really want to edit this message? In order to edit this message, the AI response will be deleted.", "Yes, remove the AI response and edit it", "No, keep it"); if (edit.HasValue && edit.Value) await this.EditLastUserBlockFunc(this.Content); } }