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 Func? RemoveBlockFunc { get; set; } [Parameter] public Func? RegenerateFunc { 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); } }