mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-11-05 01:27:21 +00:00
Fixed chats being stored when chats are stored manually or workspaces are disabled (#1145)
This commit is contained in:
1 parent
9532664a25
commit
64dc621a7d
12 files changed
+415
-71
No files matched your search
@@ -674,6 +674,9 @@ public partial class ChatComponent : MSGComponentBase
|
||||
}
|
||||
|
||||
var leftChatHadNoThread = !isFirstChat && this.loadedParameterChatId == Guid.Empty;
|
||||
if (!isFirstChat)
|
||||
await this.StopAnswerOfLeftChatAsync(this.loadedParameterChatId);
|
||||
|
||||
this.loadedParameterChatId = chatId;
|
||||
this.loadedParameterWorkspaceId = workspaceId;
|
||||
|
||||
@@ -1580,9 +1583,15 @@ public partial class ChatComponent : MSGComponentBase
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and stores the chat the composer belongs to, when there is none yet, e.g., for what an
|
||||
/// attachment brings along before the first message is sent.
|
||||
/// Creates the chat the composer belongs to, when there is none yet, e.g., for what an attachment
|
||||
/// brings along before the first message is sent.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only while chats are stored automatically, the new chat reaches the disk right away. Otherwise, it
|
||||
/// counts as unsaved, like a message typed into a new chat: a transcript made for it waits in the
|
||||
/// staging area until the user saves the chat, see WorkspaceBehaviour.StoreChatAsync, and the next
|
||||
/// start removes it when they never do.
|
||||
/// </remarks>
|
||||
/// <param name="nameWithoutInput">The name of the chat while the user typed nothing yet.</param>
|
||||
/// <returns>The chat of the composer.</returns>
|
||||
private async Task<ChatThread?> EnsureChatAsync(string nameWithoutInput)
|
||||
@@ -1615,7 +1624,11 @@ public partial class ChatComponent : MSGComponentBase
|
||||
// lists the new chat right away, together with the progress of the transcription, instead
|
||||
// of only after the first message was sent:
|
||||
//
|
||||
await this.StoreAndListChatAsync(chat);
|
||||
if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY)
|
||||
await this.StoreAndListChatAsync(chat);
|
||||
else
|
||||
this.hasUnsavedChanges = true;
|
||||
|
||||
await this.SyncForegroundChatAsync();
|
||||
return chat;
|
||||
}
|
||||
@@ -1766,7 +1779,7 @@ public partial class ChatComponent : MSGComponentBase
|
||||
this.ChatThread.SelectedToolIds = [..this.selectedToolIds];
|
||||
this.ChatThread.RuntimeSelectedToolIds = this.ToolRegistry.FilterToolIdsForProvider(this.Provider, this.selectedToolIds, this.ChatThread.RequiredOutboundDataRestriction.Restriction);
|
||||
|
||||
// From here on, the AI job stores the chat, in every storage mode:
|
||||
// From here on, the AI job stores the chat, while chats are stored automatically:
|
||||
this.unstoredChat = null;
|
||||
await this.AIJobService.TryStartChatGenerationAsync(new ChatGenerationRequest
|
||||
{
|
||||
@@ -1786,6 +1799,28 @@ public partial class ChatComponent : MSGComponentBase
|
||||
await this.AIJobService.CancelChatGenerationAsync(this.ChatThread.ChatId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the answer which is still being written in a chat the user leaves, unless chats are stored automatically.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only while chats are stored automatically, the AI job stores the answer, so that the user finds
|
||||
/// it when coming back. Otherwise, the answer would end up nowhere, and writing it would only cost
|
||||
/// the user. The user was asked before, since an answer being written counts as an unsaved change
|
||||
/// then.<br/><br/>
|
||||
///
|
||||
/// Called only where the user leaves a chat: opening another one, starting a new one, or leaving the
|
||||
/// chat page. Never when this component goes away, since the page creates it anew, e.g., whenever the
|
||||
/// workspace sidebar is shown or hidden, while the chat stays.
|
||||
/// </remarks>
|
||||
/// <param name="chatId">The chat being left.</param>
|
||||
private async Task StopAnswerOfLeftChatAsync(Guid chatId)
|
||||
{
|
||||
if (chatId == Guid.Empty || this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY)
|
||||
return;
|
||||
|
||||
await this.AIJobService.CancelChatGenerationAsync(chatId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes over the tool selection of the chat that was just loaded or handed to this component.
|
||||
/// </summary>
|
||||
@@ -1825,7 +1860,21 @@ public partial class ChatComponent : MSGComponentBase
|
||||
if (!this.CanThreadBeSaved)
|
||||
return;
|
||||
|
||||
var stagedTranscripts = this.ChatThread.Draft.MediaTranscripts.Where(transcript => transcript.IsStaged).Select(transcript => transcript.FilePath).ToHashSet(StringComparer.Ordinal);
|
||||
await this.StoreAndListChatAsync(this.ChatThread);
|
||||
|
||||
//
|
||||
// Storing moved the staged transcripts of the message being written into the chat directory,
|
||||
// see WorkspaceBehaviour.StoreChatAsync. The composer still knows them by where they were, so
|
||||
// sending the message would lose them:
|
||||
//
|
||||
if (stagedTranscripts.Count > 0)
|
||||
{
|
||||
this.ComposerState.FileAttachments.RemoveWhere(attachment => stagedTranscripts.Contains(attachment.FilePath));
|
||||
foreach (var transcript in this.ChatThread.Draft.MediaTranscripts)
|
||||
this.ComposerState.FileAttachments.Add(transcript);
|
||||
}
|
||||
|
||||
this.hasUnsavedChanges = false;
|
||||
|
||||
// A new chat is on the disk now, like any other:
|
||||
@@ -1876,9 +1925,11 @@ public partial class ChatComponent : MSGComponentBase
|
||||
|
||||
//
|
||||
// Want the user to manage the chat storage manually? In that case, we have to ask the user
|
||||
// about possible data loss:
|
||||
// about possible data loss. An answer still being written is lost as well, whenever chats are
|
||||
// not stored automatically, since leaving stops it, see StopAnswerOfLeftChatAsync:
|
||||
//
|
||||
if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_MANUALLY && this.hasUnsavedChanges && !this.IsCurrentChatStreaming)
|
||||
var storageBehavior = this.SettingsManager.ConfigurationData.Workspace.StorageBehavior;
|
||||
if ((storageBehavior is WorkspaceStorageBehavior.STORE_CHATS_MANUALLY && this.hasUnsavedChanges) || (storageBehavior is not WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY && this.IsCurrentChatStreaming))
|
||||
{
|
||||
var dialogParameters = new DialogParameters<ConfirmDialog>
|
||||
{
|
||||
@@ -1930,6 +1981,9 @@ public partial class ChatComponent : MSGComponentBase
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.ChatThread is not null)
|
||||
await this.StopAnswerOfLeftChatAsync(this.ChatThread.ChatId);
|
||||
|
||||
//
|
||||
// Reset our state:
|
||||
//
|
||||
@@ -2034,19 +2088,6 @@ public partial class ChatComponent : MSGComponentBase
|
||||
if(this.ChatThread is null)
|
||||
return;
|
||||
|
||||
if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_MANUALLY && this.hasUnsavedChanges && !this.IsCurrentChatStreaming)
|
||||
{
|
||||
var confirmationDialogParameters = new DialogParameters<ConfirmDialog>
|
||||
{
|
||||
{ x => x.Message, T("Are you sure you want to move this chat? All unsaved changes will be lost.") },
|
||||
};
|
||||
|
||||
var confirmationDialogReference = await this.DialogService.ShowAsync<ConfirmDialog>(T("Unsaved Changes"), confirmationDialogParameters, DialogOptions.FULLSCREEN);
|
||||
var confirmationDialogResult = await confirmationDialogReference.Result;
|
||||
if (confirmationDialogResult is null || confirmationDialogResult.Canceled)
|
||||
return;
|
||||
}
|
||||
|
||||
var dialogParameters = new DialogParameters<WorkspaceSelectionDialog>
|
||||
{
|
||||
{ x => x.Message, T("Please select the workspace where you want to move the chat to.") },
|
||||
@@ -2065,13 +2106,15 @@ public partial class ChatComponent : MSGComponentBase
|
||||
|
||||
//
|
||||
// Like saving and copying, the move goes through the workspace component whenever there is
|
||||
// one, so its tree shows the chat in the new place right away:
|
||||
// one, so its tree shows the chat in the new place right away. Nothing the user did not save
|
||||
// gets lost, so nothing is asked: it reaches the new place only while chats are stored
|
||||
// automatically, and otherwise stays unsaved, see WorkspaceBehaviour.MoveChatAsync:
|
||||
//
|
||||
var previousWorkspaceId = this.ChatThread!.WorkspaceId;
|
||||
if (this.Workspaces is not null)
|
||||
await this.Workspaces.MoveChatAsync(this.ChatThread!, workspaceId);
|
||||
else
|
||||
await WorkspaceBehaviour.MoveChatAsync(this.ChatThread!, workspaceId);
|
||||
await WorkspaceBehaviour.MoveChatAsync(this.ChatThread!, workspaceId, this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY);
|
||||
|
||||
await this.FollowMovedChatAsync(previousWorkspaceId);
|
||||
}
|
||||
@@ -2204,6 +2247,9 @@ public partial class ChatComponent : MSGComponentBase
|
||||
{
|
||||
await this.FlushDraftAsync();
|
||||
await this.PreserveNewChatDraftAsync(this.ChatThread is null);
|
||||
if (this.ChatThread is not null)
|
||||
await this.StopAnswerOfLeftChatAsync(this.ChatThread.ChatId);
|
||||
|
||||
this.hasUnsavedChanges = false;
|
||||
this.ComposerState.Clear();
|
||||
this.ClearWorkspaceHeaderState();
|
||||
@@ -2283,8 +2329,11 @@ public partial class ChatComponent : MSGComponentBase
|
||||
if (!this.blockActions.TryRemove(this.ChatThread, block))
|
||||
return;
|
||||
|
||||
// Like every other change, stored right away only while chats are stored automatically:
|
||||
this.hasUnsavedChanges = true;
|
||||
await this.SaveThread();
|
||||
if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY)
|
||||
await this.SaveThread();
|
||||
|
||||
this.StateHasChanged();
|
||||
}
|
||||
|
||||
@@ -2310,8 +2359,12 @@ public partial class ChatComponent : MSGComponentBase
|
||||
|
||||
// The rollback reset the AI-selected data sources, which the data source selection still shows:
|
||||
this.dataSourceSelectionComponent?.ChangeOptionWithoutSaving(this.ChatThread.DataSourceOptions, this.ChatThread.AISelectedDataSources);
|
||||
|
||||
// Like every other change, stored right away only while chats are stored automatically:
|
||||
this.hasUnsavedChanges = true;
|
||||
await this.SaveThread();
|
||||
if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY)
|
||||
await this.SaveThread();
|
||||
|
||||
this.tokenTracker?.Nudge();
|
||||
this.StateHasChanged();
|
||||
await this.inputField.FocusAsync();
|
||||
@@ -2568,7 +2621,8 @@ public partial class ChatComponent : MSGComponentBase
|
||||
this.ChatThread = this.AIJobService.TryGetLiveChatThread(snapshot.SubjectId) ?? this.ChatThread;
|
||||
if (!snapshot.IsActive)
|
||||
{
|
||||
this.hasUnsavedChanges = false;
|
||||
// The job stored the answer only while chats are stored automatically, see AIJobService.CheckpointChatAsync:
|
||||
this.hasUnsavedChanges = this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is not WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY;
|
||||
this.previousInputForbidden = true;
|
||||
}
|
||||
|
||||
@@ -2587,8 +2641,9 @@ public partial class ChatComponent : MSGComponentBase
|
||||
if(this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY)
|
||||
return Task.FromResult(new MessageBusAnswer<TResult>(true, (TResult) (object) false));
|
||||
|
||||
// Nothing stores an answer being written now, and leaving stops it, see StopAnswerOfLeftChatAsync:
|
||||
if (this.IsCurrentChatStreaming)
|
||||
return Task.FromResult(new MessageBusAnswer<TResult>(true, (TResult) (object) false));
|
||||
return Task.FromResult(new MessageBusAnswer<TResult>(true, (TResult) (object) true));
|
||||
|
||||
//
|
||||
// A message the user started counts as unsaved on its own only while there is no chat
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Text.Json;
|
||||
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Dialogs;
|
||||
using AIStudio.Settings.DataModel;
|
||||
using AIStudio.Tools.AIJobs;
|
||||
using AIStudio.Tools.Media;
|
||||
using AIStudio.Tools.Services;
|
||||
@@ -1009,11 +1010,14 @@ public partial class Workspaces : MSGComponentBase
|
||||
/// Neither asks for the target workspace nor follows the chat in the chat view: both depend on
|
||||
/// where the move was asked for. Whoever moves a chat while this component exists goes through
|
||||
/// here, though, since the tree draws what it has built before and would otherwise keep showing
|
||||
/// the chat in its old place.
|
||||
/// the chat in its old place.<br/><br/>
|
||||
///
|
||||
/// What the user did not save reaches the new place only while chats are stored automatically,
|
||||
/// see WorkspaceBehaviour.MoveChatAsync.
|
||||
/// </remarks>
|
||||
public async Task MoveChatAsync(ChatThread chat, Guid targetWorkspaceId)
|
||||
{
|
||||
await WorkspaceBehaviour.MoveChatAsync(chat, targetWorkspaceId);
|
||||
await WorkspaceBehaviour.MoveChatAsync(chat, targetWorkspaceId, this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY);
|
||||
await this.LoadTreeItemsAsync(startPrefetch: false);
|
||||
}
|
||||
|
||||
@@ -1024,9 +1028,10 @@ public partial class Workspaces : MSGComponentBase
|
||||
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:
|
||||
// The open chat is the one which moves, 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. Whether that reaches the disk in the new
|
||||
// place depends on how chats are stored, see MoveChatAsync:
|
||||
//
|
||||
var chat = this.CurrentChatThread is { } openChat && openChat.ChatId == storedChat.ChatId ? openChat : storedChat;
|
||||
if (this.AIJobService.IsChatGenerationActive(chat.ChatId))
|
||||
|
||||
Reference in new issue
Block a user