Kept media transcripts when editing a message or moving a chat (#1047)

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-10-11 12:20:44 +02:00
1 parent 3a1f586f76
commit 5ee4dfa2b5
8 files changed
+461 -28

No files matched your search

@@ -102,7 +102,7 @@
@if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is not WorkspaceStorageBehavior.DISABLE_WORKSPACES)
{
<MudTooltip Text="@T("Move the chat to a workspace, or to another if it is already in one.")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
<MudIconButton Icon="@Icons.Material.Filled.MoveToInbox" Disabled="@(!this.CanThreadBeSaved || this.IsCurrentChatStreaming)" OnClick="@this.MoveChatToWorkspace"/>
<MudIconButton Icon="@Icons.Material.Filled.MoveToInbox" Disabled="@(!this.CanThreadBeMoved)" OnClick="@this.MoveChatToWorkspace"/>
</MudTooltip>
<MudTooltip Text="@T("Copy this chat & continue in the copy.")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
@@ -549,6 +549,16 @@ public partial class ChatComponent : MSGComponentBase
return;
}
//
// The same chat in another workspace was moved there, e.g., through the workspace tree. Only
// a move changes the workspace of a chat; every other way to open one brings another chat ID:
//
if (chatId != Guid.Empty && this.loadedParameterChatId == chatId)
{
await this.FollowMovedChatAsync(this.loadedParameterWorkspaceId);
return;
}
this.loadedParameterChatId = chatId;
this.loadedParameterWorkspaceId = workspaceId;
await this.LoadedChatChanged(notifyParent: false);
@@ -673,6 +683,21 @@ public partial class ChatComponent : MSGComponentBase
private bool CanThreadBeCopied => this.CanThreadBeSaved && !this.IsCurrentChatStreaming && !this.MediaTranscriptionService.IsBusy(this.CurrentMediaImportOwner);
/// <summary>
/// Whether the open chat can move into a workspace, which is exactly when the workspace tree offers it.
/// </summary>
/// <remarks>
/// The tree lists every stored chat, even one without a message yet, such as a chat which was
/// stored right away to keep the transcript of an audio or video file. A chat not stored yet has
/// no directory to move. While an answer is generated or a transcript is made, the chat stays
/// where it is, just like in the tree. The directory check comes last, because this runs with
/// every render.
/// </remarks>
private bool CanThreadBeMoved => this.ChatThread is not null
&& !this.IsCurrentChatStreaming
&& !this.MediaTranscriptionService.IsBusy(this.CurrentMediaImportOwner)
&& WorkspaceBehaviour.IsChatExisting(new LoadChat(this.ChatThread.WorkspaceId, this.ChatThread.ChatId));
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;
@@ -1366,9 +1391,35 @@ public partial class ChatComponent : MSGComponentBase
if (workspaceId == Guid.Empty)
return;
var previousWorkspaceId = this.ChatThread!.WorkspaceId;
await WorkspaceBehaviour.MoveChatAsync(this.ChatThread!, workspaceId);
this.MarkCurrentChatAsLoadedParameter();
await this.FollowMovedChatAsync(previousWorkspaceId);
}
/// <summary>
/// Follows the open chat into the workspace it was moved to.
/// </summary>
/// <remarks>
/// A move is no other chat being opened, so the message being written stays in the composer,
/// whether the user moved the chat from here or through the workspace tree. What has to change are
/// the paths of its attachments: the move took the chat directory along, and with it the
/// transcripts of audio and video files. Left with their old paths, the composer would send
/// references to files which are gone.
/// </remarks>
/// <param name="previousWorkspaceId">The workspace the chat came from, or Guid.Empty for a temporary chat.</param>
private async Task FollowMovedChatAsync(Guid previousWorkspaceId)
{
var chatId = this.ChatThread!.ChatId;
var workspaceId = this.ChatThread.WorkspaceId;
var relocatedAttachments = this.ComposerState.FileAttachments
.Select(attachment => WorkspaceBehaviour.RelocateAttachmentAfterMove(attachment, chatId, previousWorkspaceId, workspaceId))
.ToList();
this.ComposerState.FileAttachments.Clear();
foreach (var attachment in relocatedAttachments)
this.ComposerState.FileAttachments.Add(attachment);
this.MarkCurrentChatAsLoadedParameter();
await this.SyncWorkspaceHeaderWithChatThreadAsync();
}
@@ -1558,7 +1609,7 @@ public partial class ChatComponent : MSGComponentBase
return Task.CompletedTask;
this.RestoreComposerFromTextBlock(textBlock);
this.ChatThread.Remove(block);
this.ChatThread.RemoveForEditing(block);
this.ChatThread.Remove(lastBlockContent);
this.hasUnsavedChanges = true;
this.StateHasChanged();
@@ -1574,7 +1625,7 @@ public partial class ChatComponent : MSGComponentBase
return Task.CompletedTask;
this.RestoreComposerFromTextBlock(textBlock);
this.ChatThread.Remove(block);
this.ChatThread.RemoveForEditing(block);
this.hasUnsavedChanges = true;
this.StateHasChanged();
return Task.CompletedTask;
@@ -940,10 +940,16 @@ public partial class Workspaces : MSGComponentBase
private async Task MoveChatAsync(string? chatPath)
{
var chat = await this.LoadChatAsync(chatPath, false);
if (chat is null)
var storedChat = await this.LoadChatAsync(chatPath, false);
if (storedChat is null)
return;
//
// The open chat moves as it stands on the screen, just like with the button in the chat
// itself. The copy from the disk lacks whatever is not stored yet, such as a message being
// edited, and the chat would continue with that copy afterward:
//
var chat = this.CurrentChatThread is { } openChat && openChat.ChatId == storedChat.ChatId ? openChat : storedChat;
if (this.AIJobService.IsChatGenerationActive(chat.ChatId))
return;
@@ -964,11 +970,8 @@ public partial class Workspaces : MSGComponentBase
return;
await WorkspaceBehaviour.MoveChatAsync(chat, workspaceId);
if (this.CurrentChatThread?.ChatId == chat.ChatId)
{
this.CurrentChatThread = chat;
if (ReferenceEquals(chat, this.CurrentChatThread))
await this.CurrentChatThreadChanged.InvokeAsync(this.CurrentChatThread);
}
await this.LoadTreeItemsAsync(startPrefetch: false);
}