From b2ca49ab926041cdd174a9783a04dbb8a6fdfa6d Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Fri, 3 Jan 2025 21:18:27 +0100 Subject: [PATCH] Added a button to regenerate the last AI response (#247) --- app/MindWork AI Studio/Chat/ChatThread.cs | 34 +++++++++- .../Chat/ContentBlockComponent.razor | 6 ++ .../Chat/ContentBlockComponent.razor.cs | 27 ++++++++ .../Components/ChatComponent.razor | 7 ++- .../Components/ChatComponent.razor.cs | 63 +++++++++++-------- .../wwwroot/changelog/v0.9.24.md | 3 +- 6 files changed, 109 insertions(+), 31 deletions(-) diff --git a/app/MindWork AI Studio/Chat/ChatThread.cs b/app/MindWork AI Studio/Chat/ChatThread.cs index 4e89206..4f4f573 100644 --- a/app/MindWork AI Studio/Chat/ChatThread.cs +++ b/app/MindWork AI Studio/Chat/ChatThread.cs @@ -97,17 +97,45 @@ public sealed record ChatThread logger.LogInformation(logMessage); return systemPromptText; } - + /// /// Removes a content block from this chat thread. /// /// The content block to remove. - public void Remove(IContent content) + /// Indicates whether the content block is removed for + /// regeneration purposes. True, when the content block is removed for regeneration purposes, + /// which will not remove the previous user block if it is hidden from the user. + public void Remove(IContent content, bool removeForRegenerate = false) { var block = this.Blocks.FirstOrDefault(x => x.Content == content); if(block is null) return; - + + // + // Remove the previous user block if it is hidden from the user. Otherwise, + // the experience might be confusing for the user. + // + // Explanation, using the ERI assistant as an example: + // - The ERI assistant generates for every file a hidden user prompt. + // - In the UI, the user can only see the AI's responses, not the hidden user prompts. + // - Now, the user removes one AI response + // - The hidden user prompt is still there, but the user can't see it. + // - Since the user prompt is hidden, neither is it possible to remove nor edit it. + // - This method solves this issue by removing the hidden user prompt when the AI response is removed. + // + if (block.Role is ChatRole.AI && !removeForRegenerate) + { + var sortedBlocks = this.Blocks.OrderBy(x => x.Time).ToList(); + var index = sortedBlocks.IndexOf(block); + if (index > 0) + { + var previousBlock = sortedBlocks[index - 1]; + if (previousBlock.Role is ChatRole.USER && previousBlock.HideFromUser) + this.Blocks.Remove(previousBlock); + } + } + + // Remove the block from the chat thread: this.Blocks.Remove(block); } } \ No newline at end of file diff --git a/app/MindWork AI Studio/Chat/ContentBlockComponent.razor b/app/MindWork AI Studio/Chat/ContentBlockComponent.razor index 51dd8f1..1754ca6 100644 --- a/app/MindWork AI Studio/Chat/ContentBlockComponent.razor +++ b/app/MindWork AI Studio/Chat/ContentBlockComponent.razor @@ -12,6 +12,12 @@ @this.Role.ToName() (@this.Time) + @if (this.IsLastContentBlock && this.Role is ChatRole.AI && this.RegenerateFunc is not null) + { + + + + } @if (this.RemoveBlockFunc is not null) { diff --git a/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs b/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs index 898c577..a069e67 100644 --- a/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs +++ b/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs @@ -40,10 +40,19 @@ public partial class ContentBlockComponent : ComponentBase /// [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!; @@ -143,4 +152,22 @@ public partial class ContentBlockComponent : ComponentBase 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); + } } \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/ChatComponent.razor b/app/MindWork AI Studio/Components/ChatComponent.razor index 9fd6f38..4e7753d 100644 --- a/app/MindWork AI Studio/Components/ChatComponent.razor +++ b/app/MindWork AI Studio/Components/ChatComponent.razor @@ -7,11 +7,14 @@ @if (this.ChatThread is not null) { - @foreach (var block in this.ChatThread.Blocks.OrderBy(n => n.Time)) + var blocks = this.ChatThread.Blocks.OrderBy(n => n.Time).ToList(); + for (var i = 0; i < blocks.Count; i++) { + var block = blocks[i]; + var isLastBlock = i == blocks.Count - 1; @if (!block.HideFromUser) { - + } } } diff --git a/app/MindWork AI Studio/Components/ChatComponent.razor.cs b/app/MindWork AI Studio/Components/ChatComponent.razor.cs index 4b9a469..8cfae55 100644 --- a/app/MindWork AI Studio/Components/ChatComponent.razor.cs +++ b/app/MindWork AI Studio/Components/ChatComponent.razor.cs @@ -242,7 +242,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable } } - private async Task SendMessage() + private async Task SendMessage(bool reuseLastUserPrompt = false) { if (!this.IsProviderSelected) return; @@ -252,8 +252,6 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable await this.inputField.BlurAsync(); // Create a new chat thread if necessary: - var threadName = this.ExtractThreadName(this.userInput); - if (this.ChatThread is null) { this.ChatThread = new() @@ -263,7 +261,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable SystemPrompt = SystemPrompts.DEFAULT, WorkspaceId = this.currentWorkspaceId, ChatId = Guid.NewGuid(), - Name = threadName, + Name = this.ExtractThreadName(this.userInput), Seed = this.RNG.Next(), Blocks = [], }; @@ -274,34 +272,37 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable { // Set the thread name if it is empty: if (string.IsNullOrWhiteSpace(this.ChatThread.Name)) - this.ChatThread.Name = threadName; + this.ChatThread.Name = this.ExtractThreadName(this.userInput); // Update provider and profile: this.ChatThread.SelectedProvider = this.Provider.Id; this.ChatThread.SelectedProfile = this.currentProfile.Id; } - - // - // Add the user message to the thread: - // - var time = DateTimeOffset.Now; - this.ChatThread?.Blocks.Add(new ContentBlock - { - Time = time, - ContentType = ContentType.TEXT, - Role = ChatRole.USER, - Content = new ContentText - { - Text = this.userInput, - }, - }); - // Save the chat: - if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY) + var time = DateTimeOffset.Now; + if (!reuseLastUserPrompt) { - await this.SaveThread(); - this.hasUnsavedChanges = false; - this.StateHasChanged(); + // + // Add the user message to the thread: + // + this.ChatThread?.Blocks.Add(new ContentBlock + { + Time = time, + ContentType = ContentType.TEXT, + Role = ChatRole.USER, + Content = new ContentText + { + Text = this.userInput, + }, + }); + + // Save the chat: + if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY) + { + await this.SaveThread(); + this.hasUnsavedChanges = false; + this.StateHasChanged(); + } } // @@ -581,6 +582,18 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable await this.SaveThread(); this.StateHasChanged(); } + + private async Task RegenerateBlock(IContent aiBlock) + { + if(this.ChatThread is null) + return; + + this.ChatThread.Remove(aiBlock, removeForRegenerate: true); + this.hasUnsavedChanges = true; + this.StateHasChanged(); + + await this.SendMessage(reuseLastUserPrompt: true); + } #region Overrides of MSGComponentBase diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.9.24.md b/app/MindWork AI Studio/wwwroot/changelog/v0.9.24.md index 03d2545..ce1786e 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v0.9.24.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.24.md @@ -1,2 +1,3 @@ # v0.9.24, build 199 (2025-01-xx xx:xx UTC) -- Added a button to remove a message from the chat thread. \ No newline at end of file +- Added a button to remove a message from the chat thread. +- Added a button to regenerate the last AI response. \ No newline at end of file