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