diff --git a/app/MindWork AI Studio/Components/ChatComponent.razor b/app/MindWork AI Studio/Components/ChatComponent.razor
index f1fe5039..c27291be 100644
--- a/app/MindWork AI Studio/Components/ChatComponent.razor
+++ b/app/MindWork AI Studio/Components/ChatComponent.razor
@@ -103,6 +103,10 @@
+
+
+
+
}
diff --git a/app/MindWork AI Studio/Components/ChatComponent.razor.cs b/app/MindWork AI Studio/Components/ChatComponent.razor.cs
index a28509f1..f4c017aa 100644
--- a/app/MindWork AI Studio/Components/ChatComponent.razor.cs
+++ b/app/MindWork AI Studio/Components/ChatComponent.razor.cs
@@ -649,7 +649,9 @@ public partial class ChatComponent : MSGComponentBase
}
private bool CanThreadBeSaved => this.ChatThread is not null && this.ChatThread.Blocks.Any(b => !b.HideFromUser);
-
+
+ private bool CanThreadBeCopied => this.CanThreadBeSaved && !this.IsCurrentChatStreaming && !this.MediaTranscriptionService.IsBusy(this.CurrentMediaImportOwner);
+
private string TooltipAddChatToWorkspace => string.Format(T("Start new chat in workspace '{0}'"), this.currentWorkspaceName);
private string UserInputStyle => this.SettingsManager.ConfigurationData.Confidence.ShowProviderConfidence ? this.Provider.UsedLLMProvider.GetConfidence(this.SettingsManager).SetColorStyle(this.SettingsManager) : string.Empty;
@@ -1344,7 +1346,48 @@ public partial class ChatComponent : MSGComponentBase
await this.SyncWorkspaceHeaderWithChatThreadAsync();
}
-
+
+ ///
+ /// Copies the open chat and continues in the copy.
+ ///
+ ///
+ /// Copied is the chat as it stands on the screen, not the state which was stored last. That is
+ /// why nothing is asked about unsaved changes here, unlike everywhere else a chat is left
+ /// behind: none of them are lost, they move into the copy, while the original keeps what was
+ /// stored.
+ ///
+ /// The same holds for the message being written. The copy is marked as loaded right away,
+ /// because the parameter update which follows would otherwise take it for another chat being
+ /// opened and clear the composer. Only the transcripts attached to that message are exchanged:
+ /// those of the original live in its directory, and the copy got copies of its own.
+ ///
+ private async Task CopyCurrentChat()
+ {
+ if (this.ChatThread is null || !this.CanThreadBeCopied)
+ return;
+
+ var sourceChat = this.ChatThread;
+ var copy = this.Workspaces is null
+ ? await WorkspaceBehaviour.CopyChatAsync(this.DialogService, sourceChat)
+ : await this.Workspaces.CopyChatAsync(sourceChat);
+
+ if (copy is null)
+ return;
+
+ var transcriptsOfTheOriginal = sourceChat.PendingMediaTranscripts.Select(transcript => transcript.FilePath).ToHashSet(StringComparer.Ordinal);
+ this.ComposerState.FileAttachments.RemoveWhere(attachment => transcriptsOfTheOriginal.Contains(attachment.FilePath));
+ foreach (var transcript in copy.PendingMediaTranscripts)
+ this.ComposerState.FileAttachments.Add(transcript);
+
+ this.ChatThread = copy;
+ this.hasUnsavedChanges = false;
+
+ await this.SyncForegroundChatAsync();
+ this.MarkCurrentChatAsLoadedParameter();
+ await this.SyncWorkspaceHeaderWithChatThreadAsync();
+ await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
+ }
+
private async Task LoadedChatChanged(bool notifyParent = true)
{
this.hasUnsavedChanges = false;
diff --git a/app/MindWork AI Studio/Components/Workspaces.razor.cs b/app/MindWork AI Studio/Components/Workspaces.razor.cs
index 801b197c..240f7dfb 100644
--- a/app/MindWork AI Studio/Components/Workspaces.razor.cs
+++ b/app/MindWork AI Studio/Components/Workspaces.razor.cs
@@ -660,6 +660,24 @@ public partial class Workspaces : MSGComponentBase
await this.LoadTreeItemsAsync(startPrefetch: false);
}
+ ///
+ /// Copies the given chat, after asking the user for the name of the copy, and shows the copy in the tree.
+ ///
+ /// The chat to copy, as it stands in memory.
+ /// The persisted copy. Null when the user canceled the question, in which case nothing was copied.
+ ///
+ /// Neither asks about unsaved changes nor opens the copy: which of both a caller needs depends
+ /// on where the copy was asked for.
+ ///
+ public async Task CopyChatAsync(ChatThread sourceChat)
+ {
+ var copy = await WorkspaceBehaviour.CopyChatAsync(this.DialogService, sourceChat);
+ if (copy is not null)
+ await this.LoadTreeItemsAsync(startPrefetch: false);
+
+ return copy;
+ }
+
private async Task LoadChatAsync(string? chatPath, bool switchToChat)
{
if (string.IsNullOrWhiteSpace(chatPath))
@@ -819,11 +837,10 @@ public partial class Workspaces : MSGComponentBase
}
var sourceChat = isCopyOfOpenChat ? openChat! : chat;
- var copy = await WorkspaceBehaviour.CopyChatAsync(this.DialogService, sourceChat);
+ var copy = await this.CopyChatAsync(sourceChat);
if (copy is null)
return;
- await this.LoadTreeItemsAsync(startPrefetch: false);
this.CurrentChatThread = copy;
await this.CurrentChatThreadChanged.InvokeAsync(this.CurrentChatThread);
}