diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua
index 66064dcb..a2d726f0 100644
--- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua
+++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua
@@ -12973,20 +12973,32 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::PROVIDERVALIDATION::T818893091"] =
-- Are you sure you want to delete the chat '{0}' in the workspace '{1}'?
UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T1016188706"] = "Are you sure you want to delete the chat '{0}' in the workspace '{1}'?"
+-- Copy Chat
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T1192756314"] = "Copy Chat"
+
-- Unnamed workspace
UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T1307384014"] = "Unnamed workspace"
+-- Copy
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T1703884388"] = "Copy"
+
-- Delete Chat
UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T2244038752"] = "Delete Chat"
+-- Please enter a chat name.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T2301651387"] = "Please enter a chat name."
+
-- Are you sure you want to delete the temporary chat '{0}'?
UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T3043761007"] = "Are you sure you want to delete the temporary chat '{0}'?"
-- Unnamed chat
UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T3310482275"] = "Unnamed chat"
+-- Please enter a name for the copy of your chat '{0}':
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T3323676840"] = "Please enter a name for the copy of your chat '{0}':"
+
-- Copy of {0}
UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T3365678931"] = "Copy of {0}"
--- Empty chat
-UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T4019509364"] = "Empty chat"
+-- Chat Name
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T3891063690"] = "Chat Name"
diff --git a/app/MindWork AI Studio/Components/Workspaces.razor.cs b/app/MindWork AI Studio/Components/Workspaces.razor.cs
index 73f85291..801b197c 100644
--- a/app/MindWork AI Studio/Components/Workspaces.razor.cs
+++ b/app/MindWork AI Studio/Components/Workspaces.razor.cs
@@ -819,7 +819,9 @@ public partial class Workspaces : MSGComponentBase
}
var sourceChat = isCopyOfOpenChat ? openChat! : chat;
- var copy = await WorkspaceBehaviour.CopyChatAsync(sourceChat);
+ var copy = await WorkspaceBehaviour.CopyChatAsync(this.DialogService, sourceChat);
+ if (copy is null)
+ return;
await this.LoadTreeItemsAsync(startPrefetch: false);
this.CurrentChatThread = copy;
diff --git a/app/MindWork AI Studio/Tools/WorkspaceBehaviour.cs b/app/MindWork AI Studio/Tools/WorkspaceBehaviour.cs
index 519cff7c..6a9c14da 100644
--- a/app/MindWork AI Studio/Tools/WorkspaceBehaviour.cs
+++ b/app/MindWork AI Studio/Tools/WorkspaceBehaviour.cs
@@ -789,23 +789,46 @@ public static class WorkspaceBehaviour
}
///
- /// Copies a chat into a new chat of the same workspace, including its managed transcript files.
+ /// Copies a chat into a new chat of the same workspace, including its managed transcript files,
+ /// after asking the user for the name of the copy.
///
+ /// Used to ask for the name.
/// The chat to copy. Its own files and state stay untouched.
- /// The persisted copy.
+ /// The persisted copy. Null when the user canceled the question, in which case nothing was copied.
///
+ /// This is the one place that asks for the name of a copy, so every way of copying a chat
+ /// suggests the same name and words the question the same way.
+ ///
/// The copy is written before it is returned, so the caller may open it right away. Runtime-only
- /// state of the source is not part of the copy: it is rebuilt when the copy gets loaded.
+ /// state of the source is not part of the copy: it is rebuilt when the copy gets loaded. When
+ /// the copy cannot be written, nothing of it stays behind and the error reaches the caller.
///
- public static async Task CopyChatAsync(ChatThread sourceChat)
+ public static async Task CopyChatAsync(IDialogService dialogService, ChatThread sourceChat)
{
+ var sourceName = string.IsNullOrWhiteSpace(sourceChat.Name) ? TB("Unnamed chat") : sourceChat.Name;
+ var dialogParameters = new DialogParameters
+ {
+ { x => x.Message, string.Format(TB("Please enter a name for the copy of your chat '{0}':"), sourceName) },
+ { x => x.InputHeaderText, TB("Chat Name") },
+ { x => x.UserInput, string.Format(TB("Copy of {0}"), sourceName) },
+ { x => x.ConfirmText, TB("Copy") },
+ { x => x.ConfirmColor, Color.Info },
+ { x => x.AllowEmptyInput, false },
+ { x => x.EmptyInputErrorMessage, TB("Please enter a chat name.") },
+ };
+
+ var dialogReference = await dialogService.ShowAsync(TB("Copy Chat"), dialogParameters, Dialogs.DialogOptions.FULLSCREEN);
+ var dialogResult = await dialogReference.Result;
+ if (dialogResult is null || dialogResult.Canceled)
+ return null;
+
var serializedChat = JsonSerializer.Serialize(sourceChat, JSON_OPTIONS);
var copiedChat = JsonSerializer.Deserialize(serializedChat, JSON_OPTIONS)
?? throw new InvalidOperationException("The chat could not be copied.");
copiedChat = copiedChat with
{
ChatId = Guid.NewGuid(),
- Name = string.Format(TB("Copy of {0}"), string.IsNullOrWhiteSpace(sourceChat.Name) ? TB("Empty chat") : sourceChat.Name),
+ Name = (dialogResult.Data as string)!,
};
var targetDirectory = GetChatDirectory(copiedChat.WorkspaceId, copiedChat.ChatId);