mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-09-27 01:53:36 +00:00
Added a way to duplicate a chat
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
c95cc5bacc
commit
46cd16266d
@ -3589,6 +3589,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2036185364"] = "Code"
|
||||
-- plus {0} image(s), which is more than the {1} this model accepts
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2059172343"] = "plus {0} image(s), which is more than the {1} this model accepts"
|
||||
|
||||
-- Please enter a chat name.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2301651387"] = "Please enter a chat name."
|
||||
|
||||
-- Italic
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2377171085"] = "Italic"
|
||||
|
||||
@ -3601,6 +3604,12 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2670286472"] = "Profile u
|
||||
-- The selected provider is not allowed in this chat due to data security or confidence-level requirements.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2672162875"] = "The selected provider is not allowed in this chat due to data security or confidence-level requirements."
|
||||
|
||||
-- Duplicate Chat
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2762849660"] = "Duplicate Chat"
|
||||
|
||||
-- {0} (copy)
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2930606011"] = "{0} (copy)"
|
||||
|
||||
-- Bulleted List
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2957125464"] = "Bulleted List"
|
||||
|
||||
@ -3613,12 +3622,18 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T3045856778"] = "Move Chat
|
||||
-- {0} tokens
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T3244065777"] = "{0} tokens"
|
||||
|
||||
-- Please enter a name for the copy of your chat '{0}':
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T3323676840"] = "Please enter a name for the copy of your chat '{0}':"
|
||||
|
||||
-- plus {0} image(s), which cannot be counted
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T3619858297"] = "plus {0} image(s), which cannot be counted"
|
||||
|
||||
-- Select a provider first
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T3654197869"] = "Select a provider first"
|
||||
|
||||
-- Chat Name
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T3891063690"] = "Chat Name"
|
||||
|
||||
-- Start new chat in workspace '{0}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T3928697643"] = "Start new chat in workspace '{0}'"
|
||||
|
||||
@ -3631,9 +3646,15 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T4113970938"] = "Start tem
|
||||
-- Heading
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T4231005109"] = "Heading"
|
||||
|
||||
-- Duplicate
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T4231201590"] = "Duplicate"
|
||||
|
||||
-- Please select the workspace where you want to move the chat to.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T474393241"] = "Please select the workspace where you want to move the chat to."
|
||||
|
||||
-- Duplicate this chat & continue in the copy.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T568087588"] = "Duplicate this chat & continue in the copy."
|
||||
|
||||
-- Move the chat to a workspace, or to another if it is already in one.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T636393754"] = "Move the chat to a workspace, or to another if it is already in one."
|
||||
|
||||
@ -5338,6 +5359,12 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2446263209"] = "Workspace Na
|
||||
-- Move to workspace
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2509305748"] = "Move to workspace"
|
||||
|
||||
-- Duplicate Chat
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2762849660"] = "Duplicate Chat"
|
||||
|
||||
-- {0} (copy)
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2930606011"] = "{0} (copy)"
|
||||
|
||||
-- Are you sure you want to delete the temporary chat '{0}'?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3043761007"] = "Are you sure you want to delete the temporary chat '{0}'?"
|
||||
|
||||
@ -5353,6 +5380,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3249036008"] = "There is alr
|
||||
-- Please enter a workspace name.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3288132732"] = "Please enter a workspace name."
|
||||
|
||||
-- Please enter a name for the copy of your chat '{0}':
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3323676840"] = "Please enter a name for the copy of your chat '{0}':"
|
||||
|
||||
-- Rename
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3355849203"] = "Rename"
|
||||
|
||||
@ -5380,6 +5410,12 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T4048389951"] = "Workspaces"
|
||||
-- Disappearing Chats
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T4201703117"] = "Disappearing Chats"
|
||||
|
||||
-- Are you sure you want to duplicate this chat? The copy is opened, so everything you wrote since you last saved this chat will be lost.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T4222136922"] = "Are you sure you want to duplicate this chat? The copy is opened, so everything you wrote since you last saved this chat will be lost."
|
||||
|
||||
-- Duplicate
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T4231201590"] = "Duplicate"
|
||||
|
||||
-- Please select the workspace where you want to move the chat to.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T474393241"] = "Please select the workspace where you want to move the chat to."
|
||||
|
||||
|
||||
@ -89,6 +89,13 @@
|
||||
<ChatTemplateSelection MarginLeft="" CanChatThreadBeUsedForTemplate="@this.CanThreadBeSaved" CurrentChatThread="@this.ChatThread" CurrentChatTemplate="@this.currentChatTemplate" CurrentChatTemplateChanged="@this.ChatTemplateWasChanged"/>
|
||||
|
||||
|
||||
@if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is not WorkspaceStorageBehavior.DISABLE_WORKSPACES)
|
||||
{
|
||||
<MudTooltip Text="@T("Duplicate this chat & continue in the copy.")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.ContentCopy" Disabled="@(!this.CanThreadBeSaved || this.IsCurrentChatStreaming)" OnClick="@this.DuplicateCurrentChat"/>
|
||||
</MudTooltip>
|
||||
}
|
||||
|
||||
@if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY)
|
||||
{
|
||||
<MudTooltip Text="@T("Delete this chat & start a new one.")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
|
||||
|
||||
@ -1242,6 +1242,49 @@ public partial class ChatComponent : MSGComponentBase
|
||||
await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the chat which is open into a second one and continues in that one.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Copied is the chat as it stands on the screen, not the state which was stored last, so a
|
||||
/// sentence somebody wrote a moment ago is part of the copy. That is also why nothing is asked
|
||||
/// about unsaved changes here, unlike everywhere else which leaves a chat behind: none of them
|
||||
/// are lost, they are what is being copied.
|
||||
/// </remarks>
|
||||
private async Task DuplicateCurrentChat()
|
||||
{
|
||||
if (this.ChatThread is null || this.IsCurrentChatStreaming)
|
||||
return;
|
||||
|
||||
var dialogParameters = new DialogParameters<SingleInputDialog>
|
||||
{
|
||||
{ x => x.Message, string.Format(T("Please enter a name for the copy of your chat '{0}':"), this.ChatThread.Name) },
|
||||
{ x => x.InputHeaderText, T("Chat Name") },
|
||||
{ x => x.UserInput, string.Format(T("{0} (copy)"), this.ChatThread.Name) },
|
||||
{ x => x.ConfirmText, T("Duplicate") },
|
||||
{ x => x.ConfirmColor, Color.Info },
|
||||
{ x => x.AllowEmptyInput, false },
|
||||
{ x => x.EmptyInputErrorMessage, T("Please enter a chat name.") },
|
||||
};
|
||||
|
||||
var dialogReference = await this.DialogService.ShowAsync<SingleInputDialog>(T("Duplicate Chat"), dialogParameters, DialogOptions.FULLSCREEN);
|
||||
var dialogResult = await dialogReference.Result;
|
||||
if (dialogResult is null || dialogResult.Canceled)
|
||||
return;
|
||||
|
||||
var copy = await WorkspaceBehaviour.DuplicateChatAsync(this.ChatThread, (dialogResult.Data as string)!);
|
||||
if (copy is null)
|
||||
return;
|
||||
|
||||
this.ChatThread = copy;
|
||||
this.hasUnsavedChanges = false;
|
||||
|
||||
await this.SyncForegroundChatAsync();
|
||||
this.MarkCurrentChatAsLoadedParameter();
|
||||
await this.SyncWorkspaceHeaderWithChatThreadAsync();
|
||||
await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
|
||||
}
|
||||
|
||||
private async Task MoveChatToWorkspace()
|
||||
{
|
||||
if(this.ChatThread is null)
|
||||
|
||||
@ -85,6 +85,10 @@ else
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Edit" Size="Size.Medium" Color="Color.Inherit" Disabled="@this.IsChatTreeItemBusy(treeItem)" OnClick="@(() => this.RenameChatAsync(treeItem.Path))"/>
|
||||
</MudTooltip>
|
||||
|
||||
<MudTooltip Text="@T("Duplicate")" Placement="@WORKSPACE_ITEM_TOOLTIP_PLACEMENT">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.ContentCopy" Size="Size.Medium" Color="Color.Inherit" Disabled="@this.IsChatTreeItemBusy(treeItem)" OnClick="@(() => this.DuplicateChatAsync(treeItem.Path))"/>
|
||||
</MudTooltip>
|
||||
|
||||
<MudTooltip Text="@T("Delete")" Placement="@WORKSPACE_ITEM_TOOLTIP_PLACEMENT">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete" Size="Size.Medium" Color="Color.Error" Disabled="@this.IsChatTreeItemBusy(treeItem)" OnClick="@(() => this.DeleteChatAsync(treeItem.Path))"/>
|
||||
</MudTooltip>
|
||||
|
||||
@ -745,6 +745,64 @@ public partial class Workspaces : MSGComponentBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a chat of the tree into a second one and opens it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Two questions in a row, and only the second one is always asked. The first is the one every
|
||||
/// other way out of a chat asks -- whether unsaved changes may be lost -- because the copy is
|
||||
/// opened once it exists. The second asks what the copy is called, so that two chats of the
|
||||
/// same name do not end up next to each other in the tree.
|
||||
/// </remarks>
|
||||
/// <param name="chatPath">The directory of the chat to copy.</param>
|
||||
private async Task DuplicateChatAsync(string? chatPath)
|
||||
{
|
||||
var chat = await this.LoadChatAsync(chatPath, false);
|
||||
if (chat is null)
|
||||
return;
|
||||
|
||||
if (this.AIJobService.IsChatGenerationActive(chat.ChatId))
|
||||
return;
|
||||
|
||||
if (await MessageBus.INSTANCE.SendMessageUseFirstResult<bool, bool>(this, Event.HAS_CHAT_UNSAVED_CHANGES))
|
||||
{
|
||||
var unsavedDialogParameters = new DialogParameters<ConfirmDialog>
|
||||
{
|
||||
{ x => x.Message, T("Are you sure you want to duplicate this chat? The copy is opened, so everything you wrote since you last saved this chat will be lost.") },
|
||||
};
|
||||
|
||||
var unsavedDialogReference = await this.DialogService.ShowAsync<ConfirmDialog>(T("Duplicate Chat"), unsavedDialogParameters, DialogOptions.FULLSCREEN);
|
||||
var unsavedDialogResult = await unsavedDialogReference.Result;
|
||||
if (unsavedDialogResult is null || unsavedDialogResult.Canceled)
|
||||
return;
|
||||
}
|
||||
|
||||
var dialogParameters = new DialogParameters<SingleInputDialog>
|
||||
{
|
||||
{ x => x.Message, string.Format(T("Please enter a name for the copy of your chat '{0}':"), chat.Name) },
|
||||
{ x => x.InputHeaderText, T("Chat Name") },
|
||||
{ x => x.UserInput, string.Format(T("{0} (copy)"), chat.Name) },
|
||||
{ x => x.ConfirmText, T("Duplicate") },
|
||||
{ x => x.ConfirmColor, Color.Info },
|
||||
{ x => x.AllowEmptyInput, false },
|
||||
{ x => x.EmptyInputErrorMessage, T("Please enter a chat name.") },
|
||||
};
|
||||
|
||||
var dialogReference = await this.DialogService.ShowAsync<SingleInputDialog>(T("Duplicate Chat"), dialogParameters, DialogOptions.FULLSCREEN);
|
||||
var dialogResult = await dialogReference.Result;
|
||||
if (dialogResult is null || dialogResult.Canceled)
|
||||
return;
|
||||
|
||||
var copy = await WorkspaceBehaviour.DuplicateChatAsync(chat, (dialogResult.Data as string)!);
|
||||
if (copy is null)
|
||||
return;
|
||||
|
||||
await this.LoadTreeItemsAsync(startPrefetch: false);
|
||||
|
||||
this.CurrentChatThread = copy;
|
||||
await this.CurrentChatThreadChanged.InvokeAsync(this.CurrentChatThread);
|
||||
}
|
||||
|
||||
private async Task RenameChatAsync(string? chatPath)
|
||||
{
|
||||
var chat = await this.LoadChatAsync(chatPath, false);
|
||||
|
||||
@ -3591,6 +3591,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2036185364"] = "Code"
|
||||
-- plus {0} image(s), which is more than the {1} this model accepts
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2059172343"] = "plus {0} Bild(er), also mehr als die {1}, die dieses Modell akzeptiert"
|
||||
|
||||
-- Please enter a chat name.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2301651387"] = "Bitte geben Sie einen Namen für diesen Chat ein."
|
||||
|
||||
-- Italic
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2377171085"] = "Kursiv"
|
||||
|
||||
@ -3603,6 +3606,12 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2670286472"] = "Die Profi
|
||||
-- The selected provider is not allowed in this chat due to data security or confidence-level requirements.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2672162875"] = "Der ausgewählte Anbieter ist in diesem Chat aufgrund der Datensicherheit oder der Anforderungen an das Vertrauensniveau nicht zulässig."
|
||||
|
||||
-- Duplicate Chat
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2762849660"] = "Chat duplizieren"
|
||||
|
||||
-- {0} (copy)
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2930606011"] = "{0} (Kopie)"
|
||||
|
||||
-- Bulleted List
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2957125464"] = "Aufzählungszeichen"
|
||||
|
||||
@ -3615,12 +3624,18 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T3045856778"] = "Chat in d
|
||||
-- {0} tokens
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T3244065777"] = "{0} Token"
|
||||
|
||||
-- Please enter a name for the copy of your chat '{0}':
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T3323676840"] = "Bitte geben Sie einen Namen für die Kopie Ihres Chats „{0}“ ein:"
|
||||
|
||||
-- plus {0} image(s), which cannot be counted
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T3619858297"] = "zuzüglich {0} Bild(er), die nicht gezählt werden können"
|
||||
|
||||
-- Select a provider first
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T3654197869"] = "Wähle zuerst einen Anbieter aus"
|
||||
|
||||
-- Chat Name
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T3891063690"] = "Name des Chat"
|
||||
|
||||
-- Start new chat in workspace "{0}"
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T3928697643"] = "Neuen Chat im Arbeitsbereich '{0}' starten"
|
||||
|
||||
@ -3633,9 +3648,15 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T4113970938"] = "Neuen sel
|
||||
-- Heading
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T4231005109"] = "Überschrift"
|
||||
|
||||
-- Duplicate
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T4231201590"] = "Duplizieren"
|
||||
|
||||
-- Please select the workspace where you want to move the chat to.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T474393241"] = "Bitte wählen Sie den Arbeitsbereich aus, in den Sie den Chat verschieben möchten."
|
||||
|
||||
-- Duplicate this chat & continue in the copy.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T568087588"] = "Diesen Chat duplizieren und in der Kopie weiterarbeiten."
|
||||
|
||||
-- Move the chat to a workspace, or to another if it is already in one.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T636393754"] = "Verschieben Sie den Chat in einen Arbeitsbereich oder in einen anderen, falls er sich bereits in einem befindet."
|
||||
|
||||
@ -5340,6 +5361,12 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2446263209"] = "Name des Arb
|
||||
-- Move to workspace
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2509305748"] = "In einen Arbeitsbereich verschieben"
|
||||
|
||||
-- Duplicate Chat
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2762849660"] = "Chat duplizieren"
|
||||
|
||||
-- {0} (copy)
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2930606011"] = "{0} (Kopie)"
|
||||
|
||||
-- Are you sure you want to delete the chat '{0}'?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3043761007"] = "Sind Sie sicher, dass Sie den Chat „{0}“ löschen möchten?"
|
||||
|
||||
@ -5355,6 +5382,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3249036008"] = "Es gibt bere
|
||||
-- Please enter a workspace name.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3288132732"] = "Bitte geben Sie einen Namen für diesen Arbeitsbereich ein."
|
||||
|
||||
-- Please enter a name for the copy of your chat '{0}':
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3323676840"] = "Bitte geben Sie einen Namen für die Kopie Ihres Chats „{0}“ ein:"
|
||||
|
||||
-- Rename
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3355849203"] = "Umbenennen"
|
||||
|
||||
@ -5382,6 +5412,12 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T4048389951"] = "Arbeitsberei
|
||||
-- Disappearing Chats
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T4201703117"] = "Selbstlöschende Chats"
|
||||
|
||||
-- Are you sure you want to duplicate this chat? The copy is opened, so everything you wrote since you last saved this chat will be lost.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T4222136922"] = "Möchten Sie diesen Chat wirklich duplizieren? Die Kopie wird geöffnet; alles, was Sie seit dem letzten Speichern geschrieben haben, geht dabei verloren."
|
||||
|
||||
-- Duplicate
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T4231201590"] = "Duplizieren"
|
||||
|
||||
-- Please select the workspace where you want to move the chat to.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T474393241"] = "Bitte wählen Sie den Arbeitsbereich aus, in den Sie den Chat verschieben möchten."
|
||||
|
||||
|
||||
@ -3591,6 +3591,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2036185364"] = "Code"
|
||||
-- plus {0} image(s), which is more than the {1} this model accepts
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2059172343"] = "plus {0} image(s), which is more than the {1} this model accepts"
|
||||
|
||||
-- Please enter a chat name.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2301651387"] = "Please enter a chat name."
|
||||
|
||||
-- Italic
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2377171085"] = "Italic"
|
||||
|
||||
@ -3603,6 +3606,12 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2670286472"] = "Profile u
|
||||
-- The selected provider is not allowed in this chat due to data security or confidence-level requirements.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2672162875"] = "The selected provider is not allowed in this chat due to data security or confidence-level requirements."
|
||||
|
||||
-- Duplicate Chat
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2762849660"] = "Duplicate Chat"
|
||||
|
||||
-- {0} (copy)
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2930606011"] = "{0} (copy)"
|
||||
|
||||
-- Bulleted List
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2957125464"] = "Bulleted List"
|
||||
|
||||
@ -3615,12 +3624,18 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T3045856778"] = "Move Chat
|
||||
-- {0} tokens
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T3244065777"] = "{0} tokens"
|
||||
|
||||
-- Please enter a name for the copy of your chat '{0}':
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T3323676840"] = "Please enter a name for the copy of your chat '{0}':"
|
||||
|
||||
-- plus {0} image(s), which cannot be counted
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T3619858297"] = "plus {0} image(s), which cannot be counted"
|
||||
|
||||
-- Select a provider first
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T3654197869"] = "Select a provider first"
|
||||
|
||||
-- Chat Name
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T3891063690"] = "Chat Name"
|
||||
|
||||
-- Start new chat in workspace "{0}"
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T3928697643"] = "Start new chat in workspace \"{0}\""
|
||||
|
||||
@ -3633,9 +3648,15 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T4113970938"] = "New disap
|
||||
-- Heading
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T4231005109"] = "Heading"
|
||||
|
||||
-- Duplicate
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T4231201590"] = "Duplicate"
|
||||
|
||||
-- Please select the workspace where you want to move the chat to.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T474393241"] = "Please select the workspace where you want to move the chat to."
|
||||
|
||||
-- Duplicate this chat & continue in the copy.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T568087588"] = "Duplicate this chat & continue in the copy."
|
||||
|
||||
-- Move the chat to a workspace, or to another if it is already in one.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T636393754"] = "Move the chat to a workspace, or to another if it is already in one."
|
||||
|
||||
@ -5340,6 +5361,12 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2446263209"] = "Workspace Na
|
||||
-- Move to workspace
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2509305748"] = "Move to workspace"
|
||||
|
||||
-- Duplicate Chat
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2762849660"] = "Duplicate Chat"
|
||||
|
||||
-- {0} (copy)
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2930606011"] = "{0} (copy)"
|
||||
|
||||
-- Are you sure you want to delete the chat '{0}'?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3043761007"] = "Are you sure you want to delete the chat '{0}'?"
|
||||
|
||||
@ -5355,6 +5382,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3249036008"] = "There is alr
|
||||
-- Please enter a workspace name.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3288132732"] = "Please enter a workspace name."
|
||||
|
||||
-- Please enter a name for the copy of your chat '{0}':
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3323676840"] = "Please enter a name for the copy of your chat '{0}':"
|
||||
|
||||
-- Rename
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3355849203"] = "Rename"
|
||||
|
||||
@ -5382,6 +5412,12 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T4048389951"] = "Workspaces"
|
||||
-- Disappearing Chats
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T4201703117"] = "Disappearing Chats"
|
||||
|
||||
-- Are you sure you want to duplicate this chat? The copy is opened, so everything you wrote since you last saved this chat will be lost.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T4222136922"] = "Are you sure you want to duplicate this chat? The copy is opened, so everything you wrote since you last saved this chat will be lost."
|
||||
|
||||
-- Duplicate
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T4231201590"] = "Duplicate"
|
||||
|
||||
-- Please select the workspace where you want to move the chat to.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T474393241"] = "Please select the workspace where you want to move the chat to."
|
||||
|
||||
|
||||
@ -902,6 +902,93 @@ public static class WorkspaceBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a chat into a second one of its own, in the workspace the first one is in.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The copy is made by writing the chat out and reading it back rather than by naming every
|
||||
/// field it has. What survives a restart therefore survives being duplicated, and a field
|
||||
/// somebody adds later comes along without anybody having to remember this method.
|
||||
///
|
||||
/// The files a chat owns are copied with it. A chat keeps the transcripts of the media
|
||||
/// somebody attached below its own directory, and a copy which pointed at the originals would
|
||||
/// lose them the day the original chat is deleted. What the user attached from elsewhere is
|
||||
/// not touched: those files stay where they are and both chats name the same path, which is
|
||||
/// what a second chat about the same documents should do.
|
||||
/// </remarks>
|
||||
/// <param name="source">The chat to copy. It is not changed.</param>
|
||||
/// <param name="name">The name the copy gets.</param>
|
||||
/// <returns>The copy, or null when the chat was busy or could not be read back.</returns>
|
||||
public static async Task<ChatThread?> DuplicateChatAsync(ChatThread source, string name)
|
||||
{
|
||||
var sourceDirectory = GetChatDirectory(source.WorkspaceId, source.ChatId);
|
||||
var (acquired, semaphore) = await TryAcquireChatSemaphoreAsync(source.WorkspaceId, source.ChatId, nameof(DuplicateChatAsync));
|
||||
if (!acquired)
|
||||
return null;
|
||||
|
||||
ChatThread copy;
|
||||
string targetDirectory;
|
||||
try
|
||||
{
|
||||
var readBack = JsonSerializer.Deserialize<ChatThread>(JsonSerializer.Serialize(source, JSON_OPTIONS), JSON_OPTIONS);
|
||||
if (readBack is null)
|
||||
{
|
||||
LOG.LogError("Could not read back chat '{ChatId}' while duplicating it.", source.ChatId);
|
||||
return null;
|
||||
}
|
||||
|
||||
copy = readBack with { ChatId = Guid.NewGuid(), Name = name };
|
||||
targetDirectory = GetChatDirectory(copy.WorkspaceId, copy.ChatId);
|
||||
CopyOwnedFiles(sourceDirectory, targetDirectory);
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
|
||||
//
|
||||
// Done after the source is released and before the copy is written: the paths of the
|
||||
// files just copied still name the source directory, and storing the chat with those
|
||||
// would tie the copy to a directory somebody may delete tomorrow.
|
||||
//
|
||||
UpdateAttachmentPathsAfterMove(copy, sourceDirectory, targetDirectory);
|
||||
|
||||
await StoreChatAsync(copy);
|
||||
return copy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the files a chat owns into another chat's directory.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Everything below a chat directory was written by AI Studio, which makes the directory the
|
||||
/// definition of what a chat owns -- except the two files the chat storage writes itself, and
|
||||
/// the half-written ones it leaves behind while saving. Those two are written for the copy
|
||||
/// anyway, and a temporary file belongs to the save it came from.
|
||||
/// </remarks>
|
||||
/// <param name="sourceDirectory">The directory of the chat being copied.</param>
|
||||
/// <param name="targetDirectory">The directory of the copy.</param>
|
||||
private static void CopyOwnedFiles(string sourceDirectory, string targetDirectory)
|
||||
{
|
||||
if (!Directory.Exists(sourceDirectory))
|
||||
return;
|
||||
|
||||
foreach (var filePath in Directory.EnumerateFiles(sourceDirectory, "*", SearchOption.AllDirectories))
|
||||
{
|
||||
var fileName = Path.GetFileName(filePath);
|
||||
if (fileName.StartsWith('.') && fileName.EndsWith(".tmp", StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
var relativePath = Path.GetRelativePath(sourceDirectory, filePath);
|
||||
if (relativePath.Equals("thread.json", StringComparison.OrdinalIgnoreCase) || relativePath.Equals("name", StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
var targetPath = Path.Combine(targetDirectory, relativePath);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(targetPath)!);
|
||||
File.Copy(filePath, targetPath, overwrite: false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Rewrites absolute attachment paths after moving the complete chat directory.</summary>
|
||||
private static void UpdateAttachmentPathsAfterMove(ChatThread chat, string sourceDirectory, string targetDirectory)
|
||||
{
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
- Added the context window to what AI Studio knows about a model, wherever its metadata states one.
|
||||
- Added a live read of that context window at the providers which report it, among them Mistral, Groq, OpenRouter, and self-hosted vLLM servers. You then get the window your own server was started with, not the one the model card advertises.
|
||||
- Added a token count below the message field, so you always see how much of the conversation you have used. It counts everything that travels along: your messages, the files you attached, what your data sources contributed, and the tools you offered the AI. It can be an estimate when a provider does not give AI Studio everything it needs to count exactly.
|
||||
- Added duplicating a chat, so you can try a second line of thought without losing the first one. You find it in the workspace tree next to renaming and deleting, and in the chat toolbar. AI Studio asks what the copy should be called and suggests the name of the original with "(copy)" behind it, then opens the copy for you to continue in while the original stays as it was. Everything comes along: the conversation, the settings of that chat, and the transcripts of any media you attached. Those transcripts are copied rather than shared, so deleting one of the two chats never empties the other.
|
||||
- Added a warning when your conversation holds more images than the model accepts, wherever we know that limit. The Visual Briefing assistant stops before anything is uploaded, instead of letting the provider refuse it afterward.
|
||||
- Added the context window and the image limits to the expert provider settings, next to the abilities you could already state there. Leave a field empty, and AI Studio keeps its own answer, which you see as the placeholder. IT departments can state the same numbers for the providers they roll out.
|
||||
- Added model plugins, so IT departments can describe the models their organization runs itself.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user