From 0e18cbbc58fbcc3c29a06f62fd477ead243d0762 Mon Sep 17 00:00:00 2001 From: Peer Hogeterp Date: Tue, 20 Oct 2026 13:43:21 +0200 Subject: [PATCH] Fixed starting new chats from the workspace list and the chat toolbar (#1075) Co-authored-by: Thorsten Sommer --- .../Assistants/I18N/allTexts.lua | 6 -- .../Components/ChatComponent.razor.cs | 79 +++++++++++++++---- .../Components/Workspaces.razor.cs | 49 ++++++------ app/MindWork AI Studio/Pages/Chat.razor | 9 ++- app/MindWork AI Studio/Pages/Chat.razor.cs | 18 +++++ .../plugin.lua | 6 -- .../plugin.lua | 6 -- .../wwwroot/changelog/v26.10.1.md | 4 + 8 files changed, 112 insertions(+), 65 deletions(-) diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua index d0cc820bf..55b85f91f 100644 --- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua +++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua @@ -5815,9 +5815,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1874060138"] = "Add chat" -- No chats found UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1886517101"] = "No chats found" --- Create Chat -UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1939006681"] = "Create Chat" - -- Please name your workspace: UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T201482774"] = "Please name your workspace:" @@ -5827,9 +5824,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2133593288"] = "Are you sure -- Are you sure you want to delete the workspace '{0}'? This will also delete {1} chat(s) in this workspace. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2151341762"] = "Are you sure you want to delete the workspace '{0}'? This will also delete {1} chat(s) in this workspace." --- Are you sure you want to create a another chat? All unsaved changes will be lost. -UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2237618267"] = "Are you sure you want to create a another chat? All unsaved changes will be lost." - -- Please enter a chat name. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2301651387"] = "Please enter a chat name." diff --git a/app/MindWork AI Studio/Components/ChatComponent.razor.cs b/app/MindWork AI Studio/Components/ChatComponent.razor.cs index 28d82fb62..04834d572 100644 --- a/app/MindWork AI Studio/Components/ChatComponent.razor.cs +++ b/app/MindWork AI Studio/Components/ChatComponent.razor.cs @@ -1556,8 +1556,39 @@ public partial class ChatComponent : MSGComponentBase this.unstoredChat = null; } - private async Task StartNewChat(bool useSameWorkspace = false, bool deletePreviousChat = false) + /// + /// Starts a new chat in the given workspace, on behalf of the workspace tree. + /// + /// + /// The chat starts just like one from the button for a new chat in the workspace: the user is + /// asked about unsaved changes, and the chat gets its chat template with the data sources, tools, + /// and example conversation the template asks for. + /// + /// The ID of the workspace. + public Task StartNewChatInWorkspaceAsync(Guid workspaceId) => workspaceId == Guid.Empty + ? Task.CompletedTask + : this.StartNewChat(targetWorkspaceId: workspaceId); + + /// + /// Starts a new chat in place of the open one. + /// + /// + /// Every way to start a new chat leads here: the buttons of the toolbar, a change of the chat + /// template, and the workspace tree. So every new chat gets its chat template with the data + /// sources, tools, and example conversation, its profile, and its provider in the same way, and + /// the user is asked about unsaved changes in the same way. + /// + /// True starts the new chat with a thread right away, in the workspace of the open chat. False starts it outside any workspace, and its thread comes with the first message. + /// Whether to delete the open chat first. + /// The workspace for the new chat, when the workspace tree asks for one. When given, the new chat gets a thread in that workspace, whatever useSameWorkspace says. + private async Task StartNewChat(bool useSameWorkspace = false, bool deletePreviousChat = false, Guid? targetWorkspaceId = null) { + // + // Where the new chat goes is decided right away, when it is asked for. Deleting the open chat + // below takes it out of view, and with it the workspace this component remembers for the header: + // + var workspaceOfNewChat = targetWorkspaceId ?? this.currentWorkspaceId; + // // Want the user to manage the chat storage manually? In that case, we have to ask the user // about possible data loss: @@ -1643,40 +1674,53 @@ public partial class ChatComponent : MSGComponentBase } // - // Reset the chat thread or create a new one: + // Reset the chat thread or create a new one. A target workspace always means a new chat in + // that workspace, no matter what useSameWorkspace says: // - if (!useSameWorkspace) + var startsOutsideWorkspaces = !useSameWorkspace && targetWorkspaceId is null; + if (startsOutsideWorkspaces) { // - // When the user wants to start a new chat outside the current workspace, - // we have to reset the workspace id and the workspace name. Also, we have - // to reset the chat thread: + // When the user wants to start a new chat outside the current workspace, we have to + // reset the chat thread. The workspace id and the workspace name follow below: // this.ChatThread = null; - this.ClearWorkspaceHeaderState(); } else { // - // When the user wants to start a new chat in the same workspace, we have to - // reset the chat thread only. The workspace id and the workspace name remain - // the same. The new chat reaches the disk only once there is something to keep: + // When the user wants to start a new chat in a workspace, the same one or the one the + // workspace tree asked for, we have to reset the chat thread only. The new chat reaches + // the disk only once there is something to keep. The header follows once the parent + // hands the new thread back to us: // - this.ChatThread = this.NewChatThread(string.Empty); + this.ChatThread = this.NewChatThread(string.Empty, workspaceOfNewChat); this.unstoredChat = this.ChatThread; } + // + // The parent hears about the new thread right away, before anything below waits. The thread + // is a parameter of this component, and the parent hands over its own whenever it renders, + // e.g., after it received the click on the workspace tree, or after it showed another + // workspace name. Until the parent knows the new thread, such a render brings back the chat + // being left, while the composer already holds what the chat template asks for. Marking the + // thread as loaded first keeps the parameter update from taking it for another chat being + // opened, which would clear the composer: + // + this.MarkCurrentChatAsLoadedParameter(); + await this.ChatThreadChanged.InvokeAsync(this.ChatThread); + + // Clearing the workspace name renders the parent as well, so it comes only now: + if (startsOutsideWorkspaces) + this.ClearWorkspaceHeaderState(); + this.ComposerState.ApplyTemplate(this.currentChatTemplate); // Now, the chat starts with what its template asks for, and with the chat defaults wherever // that template says nothing: await this.ApplyChatTemplateToolSelectionAsync(); this.ApplyAutomaticDataSourceOptions(); - - // Notify the parent component about the change: await this.SyncForegroundChatAsync(); - this.MarkCurrentChatAsLoadedParameter(); - await this.ChatThreadChanged.InvokeAsync(this.ChatThread); } private async Task MoveChatToWorkspace() @@ -2118,8 +2162,9 @@ public partial class ChatComponent : MSGComponentBase /// a workspace replaces them right afterward with those its chat template asks for. /// /// The name of the thread. + /// The workspace of the thread. Without one, the thread goes to the workspace of the open chat. /// The new thread. - private ChatThread NewChatThread(string name) => new() + private ChatThread NewChatThread(string name, Guid? workspaceId = null) => new() { DataSourceOptions = this.earlyDataSourceOptions, IncludeDateTime = true, @@ -2128,7 +2173,7 @@ public partial class ChatComponent : MSGComponentBase SelectedChatTemplate = this.currentChatTemplate.Id, SelectedToolIds = [..this.selectedToolIds], SystemPrompt = SystemPrompts.DEFAULT, - WorkspaceId = this.currentWorkspaceId, + WorkspaceId = workspaceId ?? this.currentWorkspaceId, ChatId = Guid.NewGuid(), Name = name, Blocks = this.currentChatTemplate == ChatTemplate.NO_CHAT_TEMPLATE ? [] : this.currentChatTemplate.ExampleConversation.Select(block => block.DeepClone()).ToList(), diff --git a/app/MindWork AI Studio/Components/Workspaces.razor.cs b/app/MindWork AI Studio/Components/Workspaces.razor.cs index 7099ef654..552e05641 100644 --- a/app/MindWork AI Studio/Components/Workspaces.razor.cs +++ b/app/MindWork AI Studio/Components/Workspaces.razor.cs @@ -33,6 +33,17 @@ public partial class Workspaces : MSGComponentBase [Parameter] public EventCallback CurrentChatThreadChanged { get; set; } + /// + /// Asks the page to start a new chat in the workspace with the given ID. + /// + /// + /// The page hands the request to its chat component. A message on the bus would not do: the bus + /// also serves the chat components of windows which were reloaded long ago, and each of them would + /// start a chat of its own, storing what it still holds on its way. + /// + [Parameter] + public EventCallback StartNewChatRequested { get; set; } + [Parameter] public bool ExpandRootNodes { get; set; } = true; @@ -1040,36 +1051,20 @@ public partial class Workspaces : MSGComponentBase await this.CurrentChatThreadChanged.InvokeAsync(this.CurrentChatThread); } + /// + /// Asks the chat component to start a new chat in the given workspace. + /// + /// The directory of the workspace. + /// + /// The chat component starts the chat just like its own button for a new chat in the workspace: + /// it asks about unsaved changes, applies the chat template with its data sources, tools, and + /// example conversation, and stores the chat only once it has content. Until then, the chat is + /// missing from the tree. + /// private async Task AddChatAsync(string workspacePath) { - if (await MessageBus.INSTANCE.SendMessageUseFirstResult(this, Event.HAS_CHAT_UNSAVED_CHANGES)) - { - var dialogParameters = new DialogParameters - { - { x => x.Message, T("Are you sure you want to create a another chat? All unsaved changes will be lost.") }, - }; - - var dialogReference = await this.DialogService.ShowAsync(T("Create Chat"), dialogParameters, DialogOptions.FULLSCREEN); - var dialogResult = await dialogReference.Result; - if (dialogResult is null || dialogResult.Canceled) - return; - } - var workspaceId = Guid.Parse(Path.GetFileName(workspacePath)); - var chat = new ChatThread - { - WorkspaceId = workspaceId, - ChatId = Guid.NewGuid(), - Name = string.Empty, - SystemPrompt = SystemPrompts.DEFAULT, - Blocks = [], - }; - - var chatPath = Path.Join(workspacePath, chat.ChatId.ToString()); - - await WorkspaceBehaviour.StoreChatAsync(chat); - await this.LoadChatAsync(chatPath, switchToChat: true); - await this.LoadTreeItemsAsync(startPrefetch: false); + await this.StartNewChatRequested.InvokeAsync(workspaceId); } #region Overrides of MSGComponentBase diff --git a/app/MindWork AI Studio/Pages/Chat.razor b/app/MindWork AI Studio/Pages/Chat.razor index b9db7708b..e6f675084 100644 --- a/app/MindWork AI Studio/Pages/Chat.razor +++ b/app/MindWork AI Studio/Pages/Chat.razor @@ -62,7 +62,7 @@ - + } @@ -88,13 +88,14 @@ - + } - + } diff --git a/app/MindWork AI Studio/Pages/Chat.razor.cs b/app/MindWork AI Studio/Pages/Chat.razor.cs index 9c13a4188..91875eb9c 100644 --- a/app/MindWork AI Studio/Pages/Chat.razor.cs +++ b/app/MindWork AI Studio/Pages/Chat.razor.cs @@ -37,6 +37,7 @@ public partial class Chat : MSGComponentBase private bool workspaceSearchVisible; private string currentWorkspaceName = string.Empty; private Workspaces? workspaces; + private ChatComponent? chatComponent; private double splitterPosition = 30; private bool skipRenderAfterSplitterChange; private readonly ChatComposerState composerState = new(); @@ -167,6 +168,23 @@ public partial class Chat : MSGComponentBase await this.workspaces.ToggleSearchAsync(); } + /// + /// Starts a new chat in a workspace, when the workspace tree asks for one. + /// + /// + /// The page reads its reference to the chat component only now, when the user clicked. The chat + /// component is created anew whenever the workspace sidebar is shown or hidden, so a reference + /// taken earlier could point to one which is gone. + /// + /// The ID of the workspace. + private async Task StartNewChatInWorkspace(Guid workspaceId) + { + if (this.chatComponent is null) + return; + + await this.chatComponent.StartNewChatInWorkspaceAsync(workspaceId); + } + #region Overrides of MSGComponentBase protected override void DisposeResources() diff --git a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua index 8c9a4c08a..c0deec866 100644 --- a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua +++ b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua @@ -5817,9 +5817,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1874060138"] = "Chat hinzuf -- No chats found UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1886517101"] = "Keine Chats gefunden" --- Create Chat -UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1939006681"] = "Chat erstellen" - -- Please name your workspace: UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T201482774"] = "Bitte benennen Sie Ihren Arbeitsbereich:" @@ -5829,9 +5826,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2133593288"] = "Möchten Sie -- Are you sure you want to delete the workspace '{0}'? This will also delete {1} chat(s) in this workspace. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2151341762"] = "Möchten Sie den Arbeitsbereich „{0}“ wirklich löschen? Dadurch werden auch {1} Chat(s) in diesem Arbeitsbereich gelöscht." --- Are you sure you want to create a another chat? All unsaved changes will be lost. -UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2237618267"] = "Möchten Sie wirklich einen neuen Chat erstellen? Alle nicht gespeicherten Änderungen gehen verloren." - -- Please enter a chat name. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2301651387"] = "Bitte geben Sie einen Namen für diesen Chat ein." diff --git a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua index 6cc06d357..df4c663f7 100644 --- a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua +++ b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua @@ -5817,9 +5817,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1874060138"] = "Add chat" -- No chats found UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1886517101"] = "No chats found" --- Create Chat -UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1939006681"] = "Create Chat" - -- Please name your workspace: UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T201482774"] = "Please name your workspace:" @@ -5829,9 +5826,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2133593288"] = "Are you sure -- Are you sure you want to delete the workspace '{0}'? This will also delete {1} chat(s) in this workspace. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2151341762"] = "Are you sure you want to delete the workspace '{0}'? This will also delete {1} chat(s) in this workspace." --- Are you sure you want to create a another chat? All unsaved changes will be lost. -UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2237618267"] = "Are you sure you want to create a another chat? All unsaved changes will be lost." - -- Please enter a chat name. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2301651387"] = "Please enter a chat name." diff --git a/app/MindWork AI Studio/wwwroot/changelog/v26.10.1.md b/app/MindWork AI Studio/wwwroot/changelog/v26.10.1.md index 09fe47763..53102ac85 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v26.10.1.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v26.10.1.md @@ -46,6 +46,7 @@ - Improved adding a local data source when you have not configured an embedding provider yet. Until now, you only found out when saving the dialog, after filling in everything else. The data source settings now tell you right away and let you add an embedding provider right there, without losing what you entered. - Improved the dialogs for managing your data sources, profiles, and chat templates, which you open, for example, from a chat. The buttons for adding and importing now stay at the bottom of the dialog, so you no longer need to scroll past a long list to reach them. Thanks, Paul Koudelka (`PaulKoudelka`), for this contribution. - Changed the banner on the start page to a fresh design that shows what AI Studio can do today. +- Changed when a chat you add to a workspace in the workspace list appears there. Like any other new chat, it now appears once there is something to keep, for example, your first message, instead of right away. - Fixed a warning about the confidence level that stayed in the dialog for adding a local data source, even after you chose an embedding provider that meets it. The dialog now checks again whenever you change either of them, so a warning also appears when you switch to a provider that falls short. - Fixed the list of data sources squeezing their names into a very narrow column when you opened it from a chat or from the background embeddings page, so the names were hard to read. - Fixed two confidence levels sharing the same name in the German user interface. Moderate is called "Mäßig" again, and Medium stays "Mittel". @@ -76,6 +77,9 @@ - Fixed the workspace list no longer showing the chats of your workspaces after you moved a chat to another workspace. - Fixed a chat you moved from the disappearing chats into a workspace still being listed among the disappearing chats. - Fixed a new chat not appearing in the workspace list while AI Studio transcribed an audio or video file you had attached to it. +- Fixed a chat you added to a workspace in the workspace list missing what your chat template preselects, such as data sources, tools, and the example conversation. The chat now starts with all of it, just like a chat you start with the button for a new chat in the same workspace. +- Fixed starting a new chat sometimes keeping the chat you had open, with only the text and attachments of your chat template added to your message. This happened when you had chosen to always use the default chat provider for new chats and your chat template selects tools. +- Fixed the button "Delete this chat & start a new one" turning the new chat into a disappearing chat when the deleted chat belonged to a workspace. The new chat now stays in the workspace of the deleted chat. - Fixed a deleted workspace coming back as "Unnamed workspace" when one of its chats was still open or being answered. AI Studio now deletes a workspace only when none of its chats is being answered or transcribed, and it closes the chat you had open in it. - Fixed a chat you started by attaching an audio or video file getting a German name, regardless of the language you use AI Studio in. - Fixed AI Studio sometimes warning about unsaved changes when you left a chat in which you had only selected a chat template, without writing anything yourself.