mirror of
				https://github.com/MindWorkAI/AI-Studio.git
				synced 2025-11-04 11:40:21 +00:00 
			
		
		
		
	Added a button to regenerate the last AI response (#247)
This commit is contained in:
		
							parent
							
								
									5e445f09fa
								
							
						
					
					
						commit
						b2ca49ab92
					
				@ -102,12 +102,40 @@ public sealed record ChatThread
 | 
				
			|||||||
    /// Removes a content block from this chat thread.
 | 
					    /// Removes a content block from this chat thread.
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    /// <param name="content">The content block to remove.</param>
 | 
					    /// <param name="content">The content block to remove.</param>
 | 
				
			||||||
    public void Remove(IContent content)
 | 
					    /// <param name="removeForRegenerate">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.</param>
 | 
				
			||||||
 | 
					    public void Remove(IContent content, bool removeForRegenerate = false)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var block = this.Blocks.FirstOrDefault(x => x.Content == content);
 | 
					        var block = this.Blocks.FirstOrDefault(x => x.Content == content);
 | 
				
			||||||
        if(block is null)
 | 
					        if(block is null)
 | 
				
			||||||
            return;
 | 
					            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);
 | 
					        this.Blocks.Remove(block);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -12,6 +12,12 @@
 | 
				
			|||||||
            <MudText Typo="Typo.body1">@this.Role.ToName() (@this.Time)</MudText>
 | 
					            <MudText Typo="Typo.body1">@this.Role.ToName() (@this.Time)</MudText>
 | 
				
			||||||
        </CardHeaderContent>
 | 
					        </CardHeaderContent>
 | 
				
			||||||
        <CardHeaderActions>
 | 
					        <CardHeaderActions>
 | 
				
			||||||
 | 
					            @if (this.IsLastContentBlock && this.Role is ChatRole.AI && this.RegenerateFunc is not null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                <MudTooltip Text="Regenerate" Placement="Placement.Bottom">
 | 
				
			||||||
 | 
					                    <MudIconButton Icon="@Icons.Material.Filled.Recycling" Color="Color.Tertiary" OnClick="@this.RegenerateBlock"/>
 | 
				
			||||||
 | 
					                </MudTooltip>
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            @if (this.RemoveBlockFunc is not null)
 | 
					            @if (this.RemoveBlockFunc is not null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                <MudTooltip Text="Removes this block" Placement="Placement.Bottom">
 | 
					                <MudTooltip Text="Removes this block" Placement="Placement.Bottom">
 | 
				
			||||||
 | 
				
			|||||||
@ -41,9 +41,18 @@ public partial class ContentBlockComponent : ComponentBase
 | 
				
			|||||||
    [Parameter]
 | 
					    [Parameter]
 | 
				
			||||||
    public string Class { get; set; } = string.Empty;
 | 
					    public string Class { get; set; } = string.Empty;
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					    [Parameter]
 | 
				
			||||||
 | 
					    public bool IsLastContentBlock { get; set; } = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Parameter]
 | 
					    [Parameter]
 | 
				
			||||||
    public Func<IContent, Task>? RemoveBlockFunc { get; set; }
 | 
					    public Func<IContent, Task>? RemoveBlockFunc { get; set; }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					    [Parameter]
 | 
				
			||||||
 | 
					    public Func<IContent, Task>? RegenerateFunc { get; set; }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    [Parameter]
 | 
				
			||||||
 | 
					    public Func<bool> RegenerateEnabled { get; set; } = () => false;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    [Inject]
 | 
					    [Inject]
 | 
				
			||||||
    private RustService RustService { get; init; } = null!;
 | 
					    private RustService RustService { get; init; } = null!;
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
@ -143,4 +152,22 @@ public partial class ContentBlockComponent : ComponentBase
 | 
				
			|||||||
        if (remove.HasValue && remove.Value)
 | 
					        if (remove.HasValue && remove.Value)
 | 
				
			||||||
            await this.RemoveBlockFunc(this.Content);
 | 
					            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);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -7,11 +7,14 @@
 | 
				
			|||||||
    <ChildContent>
 | 
					    <ChildContent>
 | 
				
			||||||
        @if (this.ChatThread is not null)
 | 
					        @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)
 | 
					                @if (!block.HideFromUser)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    <ContentBlockComponent Role="@block.Role" Type="@block.ContentType" Time="@block.Time" Content="@block.Content" RemoveBlockFunc="@this.RemoveBlock"/>
 | 
					                    <ContentBlockComponent Role="@block.Role" Type="@block.ContentType" Time="@block.Time" Content="@block.Content" RemoveBlockFunc="@this.RemoveBlock" IsLastContentBlock="@isLastBlock" RegenerateFunc="@this.RegenerateBlock" RegenerateEnabled="@(() => this.IsProviderSelected)"/>
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -242,7 +242,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    private async Task SendMessage()
 | 
					    private async Task SendMessage(bool reuseLastUserPrompt = false)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        if (!this.IsProviderSelected)
 | 
					        if (!this.IsProviderSelected)
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
@ -252,8 +252,6 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
 | 
				
			|||||||
        await this.inputField.BlurAsync();
 | 
					        await this.inputField.BlurAsync();
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        // Create a new chat thread if necessary:
 | 
					        // Create a new chat thread if necessary:
 | 
				
			||||||
        var threadName = this.ExtractThreadName(this.userInput);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (this.ChatThread is null)
 | 
					        if (this.ChatThread is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            this.ChatThread = new()
 | 
					            this.ChatThread = new()
 | 
				
			||||||
@ -263,7 +261,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
 | 
				
			|||||||
                SystemPrompt = SystemPrompts.DEFAULT,
 | 
					                SystemPrompt = SystemPrompts.DEFAULT,
 | 
				
			||||||
                WorkspaceId = this.currentWorkspaceId,
 | 
					                WorkspaceId = this.currentWorkspaceId,
 | 
				
			||||||
                ChatId = Guid.NewGuid(),
 | 
					                ChatId = Guid.NewGuid(),
 | 
				
			||||||
                Name = threadName,
 | 
					                Name = this.ExtractThreadName(this.userInput),
 | 
				
			||||||
                Seed = this.RNG.Next(),
 | 
					                Seed = this.RNG.Next(),
 | 
				
			||||||
                Blocks = [],
 | 
					                Blocks = [],
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
@ -274,17 +272,19 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            // Set the thread name if it is empty:
 | 
					            // Set the thread name if it is empty:
 | 
				
			||||||
            if (string.IsNullOrWhiteSpace(this.ChatThread.Name))
 | 
					            if (string.IsNullOrWhiteSpace(this.ChatThread.Name))
 | 
				
			||||||
                this.ChatThread.Name = threadName;
 | 
					                this.ChatThread.Name = this.ExtractThreadName(this.userInput);
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            // Update provider and profile:
 | 
					            // Update provider and profile:
 | 
				
			||||||
            this.ChatThread.SelectedProvider = this.Provider.Id;
 | 
					            this.ChatThread.SelectedProvider = this.Provider.Id;
 | 
				
			||||||
            this.ChatThread.SelectedProfile = this.currentProfile.Id;
 | 
					            this.ChatThread.SelectedProfile = this.currentProfile.Id;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var time = DateTimeOffset.Now;
 | 
				
			||||||
 | 
					        if (!reuseLastUserPrompt)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
            //
 | 
					            //
 | 
				
			||||||
            // Add the user message to the thread:
 | 
					            // Add the user message to the thread:
 | 
				
			||||||
            //
 | 
					            //
 | 
				
			||||||
        var time = DateTimeOffset.Now;
 | 
					 | 
				
			||||||
            this.ChatThread?.Blocks.Add(new ContentBlock
 | 
					            this.ChatThread?.Blocks.Add(new ContentBlock
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                Time = time,
 | 
					                Time = time,
 | 
				
			||||||
@ -303,6 +303,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
 | 
				
			|||||||
                this.hasUnsavedChanges = false;
 | 
					                this.hasUnsavedChanges = false;
 | 
				
			||||||
                this.StateHasChanged();
 | 
					                this.StateHasChanged();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        //
 | 
					        //
 | 
				
			||||||
        // Add the AI response to the thread:
 | 
					        // Add the AI response to the thread:
 | 
				
			||||||
@ -582,6 +583,18 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
 | 
				
			|||||||
        this.StateHasChanged();
 | 
					        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
 | 
					    #region Overrides of MSGComponentBase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public override async Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
 | 
					    public override async Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
 | 
				
			||||||
 | 
				
			|||||||
@ -1,2 +1,3 @@
 | 
				
			|||||||
# v0.9.24, build 199 (2025-01-xx xx:xx UTC)
 | 
					# v0.9.24, build 199 (2025-01-xx xx:xx UTC)
 | 
				
			||||||
- Added a button to remove a message from the chat thread.
 | 
					- Added a button to remove a message from the chat thread.
 | 
				
			||||||
 | 
					- Added a button to regenerate the last AI response.
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user