From ceac9cefe585d9f20e64b52bdd90b2d9144c1b77 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Fri, 15 Nov 2024 21:22:57 +0100 Subject: [PATCH] Fixed bugs related to storing and loading chats (#211) --- .../BiasDay/BiasOfTheDayAssistant.razor.cs | 2 +- .../Components/Workspaces.razor.cs | 68 ++------------- app/MindWork AI Studio/Pages/Chat.razor.cs | 29 ++++--- .../Tools/WorkspaceBehaviour.cs | 84 +++++++++++++++++++ .../wwwroot/changelog/v0.9.20.md | 2 + 5 files changed, 111 insertions(+), 74 deletions(-) create mode 100644 app/MindWork AI Studio/Tools/WorkspaceBehaviour.cs create mode 100644 app/MindWork AI Studio/wwwroot/changelog/v0.9.20.md diff --git a/app/MindWork AI Studio/Assistants/BiasDay/BiasOfTheDayAssistant.razor.cs b/app/MindWork AI Studio/Assistants/BiasDay/BiasOfTheDayAssistant.razor.cs index a7a750cf..3ad8cc92 100644 --- a/app/MindWork AI Studio/Assistants/BiasDay/BiasOfTheDayAssistant.razor.cs +++ b/app/MindWork AI Studio/Assistants/BiasDay/BiasOfTheDayAssistant.razor.cs @@ -128,7 +128,7 @@ public partial class BiasOfTheDayAssistant : AssistantBaseCore ChatId = this.SettingsManager.ConfigurationData.BiasOfTheDay.BiasOfTheDayChatId, }; - if (Workspaces.IsChatExisting(biasChat)) + if (WorkspaceBehaviour.IsChatExisting(biasChat)) { MessageBus.INSTANCE.DeferMessage(this, Event.LOAD_CHAT, biasChat); this.NavigationManager.NavigateTo(Routes.CHAT); diff --git a/app/MindWork AI Studio/Components/Workspaces.razor.cs b/app/MindWork AI Studio/Components/Workspaces.razor.cs index 48d77e22..84181dbb 100644 --- a/app/MindWork AI Studio/Components/Workspaces.razor.cs +++ b/app/MindWork AI Studio/Components/Workspaces.razor.cs @@ -1,6 +1,5 @@ using System.Text; using System.Text.Json; -using System.Text.Json.Serialization; using AIStudio.Chat; using AIStudio.Dialogs; @@ -41,18 +40,6 @@ public partial class Workspaces : ComponentBase private const Placement WORKSPACE_ITEM_TOOLTIP_PLACEMENT = Placement.Bottom; public static readonly Guid WORKSPACE_ID_BIAS = Guid.Parse("82050a4e-ee92-43d7-8ee5-ab512f847e02"); - private static readonly JsonSerializerOptions JSON_OPTIONS = new() - { - WriteIndented = true, - AllowTrailingCommas = true, - PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, - DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, - PropertyNameCaseInsensitive = true, - Converters = - { - new JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseUpper), - } - }; private readonly List> treeItems = new(); @@ -160,16 +147,6 @@ public partial class Workspaces : ComponentBase return result; } - public async Task LoadWorkspaceName(Guid workspaceId) - { - if(workspaceId == Guid.Empty) - return string.Empty; - - var workspacePath = Path.Join(SettingsManager.DataDirectory, "workspaces", workspaceId.ToString()); - var workspaceNamePath = Path.Join(workspacePath, "name"); - return await File.ReadAllTextAsync(workspaceNamePath, Encoding.UTF8); - } - private async Task>> LoadWorkspaces() { var workspaces = new List>(); @@ -261,22 +238,7 @@ public partial class Workspaces : ComponentBase public async Task StoreChat(ChatThread chat, bool reloadTreeItems = true) { - string chatDirectory; - if (chat.WorkspaceId == Guid.Empty) - chatDirectory = Path.Join(SettingsManager.DataDirectory, "tempChats", chat.ChatId.ToString()); - else - chatDirectory = Path.Join(SettingsManager.DataDirectory, "workspaces", chat.WorkspaceId.ToString(), chat.ChatId.ToString()); - - // Ensure the directory exists: - Directory.CreateDirectory(chatDirectory); - - // Save the chat name: - var chatNamePath = Path.Join(chatDirectory, "name"); - await File.WriteAllTextAsync(chatNamePath, chat.Name); - - // Save the thread as thread.json: - var chatPath = Path.Join(chatDirectory, "thread.json"); - await File.WriteAllTextAsync(chatPath, JsonSerializer.Serialize(chat, JSON_OPTIONS), Encoding.UTF8); + await WorkspaceBehaviour.StoreChat(chat); // Reload the tree items: if(reloadTreeItems) @@ -284,24 +246,6 @@ public partial class Workspaces : ComponentBase this.StateHasChanged(); } - - public async Task LoadChat(LoadChat loadChat) - { - var chatPath = loadChat.WorkspaceId == Guid.Empty - ? Path.Join(SettingsManager.DataDirectory, "tempChats", loadChat.ChatId.ToString()) - : Path.Join(SettingsManager.DataDirectory, "workspaces", loadChat.WorkspaceId.ToString(), loadChat.ChatId.ToString()); - - await this.LoadChat(chatPath, switchToChat: true); - } - - public static bool IsChatExisting(LoadChat loadChat) - { - var chatPath = loadChat.WorkspaceId == Guid.Empty - ? Path.Join(SettingsManager.DataDirectory, "tempChats", loadChat.ChatId.ToString()) - : Path.Join(SettingsManager.DataDirectory, "workspaces", loadChat.WorkspaceId.ToString(), loadChat.ChatId.ToString()); - - return Directory.Exists(chatPath); - } private async Task LoadChat(string? chatPath, bool switchToChat) { @@ -328,7 +272,7 @@ public partial class Workspaces : ComponentBase try { var chatData = await File.ReadAllTextAsync(Path.Join(chatPath, "thread.json"), Encoding.UTF8); - var chat = JsonSerializer.Deserialize(chatData, JSON_OPTIONS); + var chat = JsonSerializer.Deserialize(chatData, WorkspaceBehaviour.JSON_OPTIONS); if (switchToChat) { this.CurrentChatThread = chat; @@ -354,7 +298,7 @@ public partial class Workspaces : ComponentBase if (askForConfirmation) { - var workspaceName = await this.LoadWorkspaceName(chat.WorkspaceId); + var workspaceName = await WorkspaceBehaviour.LoadWorkspaceName(chat.WorkspaceId); var dialogParameters = new DialogParameters { { @@ -419,7 +363,7 @@ public partial class Workspaces : ComponentBase return; var workspaceId = Guid.Parse(Path.GetFileName(workspacePath)); - var workspaceName = await this.LoadWorkspaceName(workspaceId); + var workspaceName = await WorkspaceBehaviour.LoadWorkspaceName(workspaceId); var dialogParameters = new DialogParameters { { "Message", $"Please enter a new or edit the name for your workspace '{workspaceName}':" }, @@ -482,7 +426,7 @@ public partial class Workspaces : ComponentBase return; var workspaceId = Guid.Parse(Path.GetFileName(workspacePath)); - var workspaceName = await this.LoadWorkspaceName(workspaceId); + var workspaceName = await WorkspaceBehaviour.LoadWorkspaceName(workspaceId); // Determine how many chats are in the workspace: var chatCount = Directory.EnumerateDirectories(workspacePath).Count(); @@ -572,7 +516,7 @@ public partial class Workspaces : ComponentBase ChatId = Guid.NewGuid(), Name = string.Empty, Seed = this.RNG.Next(), - SystemPrompt = "You are a helpful assistant!", + SystemPrompt = SystemPrompts.DEFAULT, Blocks = [], }; diff --git a/app/MindWork AI Studio/Pages/Chat.razor.cs b/app/MindWork AI Studio/Pages/Chat.razor.cs index 6452dc9b..821abb51 100644 --- a/app/MindWork AI Studio/Pages/Chat.razor.cs +++ b/app/MindWork AI Studio/Pages/Chat.razor.cs @@ -112,18 +112,19 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable protected override async Task OnAfterRenderAsync(bool firstRender) { - if (firstRender && this.workspaces is not null && this.chatThread is not null && this.mustStoreChat) + if (firstRender && this.chatThread is not null && this.mustStoreChat) { this.mustStoreChat = false; - await this.workspaces.StoreChat(this.chatThread, false); + await WorkspaceBehaviour.StoreChat(this.chatThread); this.currentWorkspaceId = this.chatThread.WorkspaceId; - this.currentWorkspaceName = await this.workspaces.LoadWorkspaceName(this.chatThread.WorkspaceId); + this.currentWorkspaceName = await WorkspaceBehaviour.LoadWorkspaceName(this.chatThread.WorkspaceId); } - if (firstRender && this.workspaces is not null && this.mustLoadChat) + if (firstRender && this.mustLoadChat) { this.mustLoadChat = false; - await this.workspaces.LoadChat(this.loadChat); + this.chatThread = await WorkspaceBehaviour.LoadChat(this.loadChat); + this.StateHasChanged(); } if(this.mustScrollToBottomAfterRender) @@ -323,16 +324,22 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable private async Task SaveThread() { - if(this.workspaces is null) - return; - if(this.chatThread is null) return; if (!this.CanThreadBeSaved) return; + + // + // When the workspace component is visible, we store the chat + // through the workspace component. The advantage of this is that + // the workspace gets updated automatically when the chat is saved. + // + if (this.workspaces is not null) + await this.workspaces.StoreChat(this.chatThread); + else + await WorkspaceBehaviour.StoreChat(this.chatThread); - await this.workspaces.StoreChat(this.chatThread); this.hasUnsavedChanges = false; } @@ -462,7 +469,7 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable await this.SaveThread(); this.currentWorkspaceId = this.chatThread.WorkspaceId; - this.currentWorkspaceName = await this.workspaces.LoadWorkspaceName(this.chatThread.WorkspaceId); + this.currentWorkspaceName = await WorkspaceBehaviour.LoadWorkspaceName(this.chatThread.WorkspaceId); } private async Task LoadedChatChanged() @@ -474,7 +481,7 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable this.hasUnsavedChanges = false; this.userInput = string.Empty; this.currentWorkspaceId = this.chatThread?.WorkspaceId ?? Guid.Empty; - this.currentWorkspaceName = this.chatThread is null ? string.Empty : await this.workspaces.LoadWorkspaceName(this.chatThread.WorkspaceId); + this.currentWorkspaceName = this.chatThread is null ? string.Empty : await WorkspaceBehaviour.LoadWorkspaceName(this.chatThread.WorkspaceId); this.userInput = string.Empty; if (this.SettingsManager.ConfigurationData.Chat.ShowLatestMessageAfterLoading) diff --git a/app/MindWork AI Studio/Tools/WorkspaceBehaviour.cs b/app/MindWork AI Studio/Tools/WorkspaceBehaviour.cs new file mode 100644 index 00000000..269768a0 --- /dev/null +++ b/app/MindWork AI Studio/Tools/WorkspaceBehaviour.cs @@ -0,0 +1,84 @@ +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; + +using AIStudio.Chat; +using AIStudio.Settings; + +namespace AIStudio.Tools; + +public static class WorkspaceBehaviour +{ + public static readonly JsonSerializerOptions JSON_OPTIONS = new() + { + WriteIndented = true, + AllowTrailingCommas = true, + PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, + DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = true, + Converters = + { + new JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseUpper), + } + }; + + public static bool IsChatExisting(LoadChat loadChat) + { + var chatPath = loadChat.WorkspaceId == Guid.Empty + ? Path.Join(SettingsManager.DataDirectory, "tempChats", loadChat.ChatId.ToString()) + : Path.Join(SettingsManager.DataDirectory, "workspaces", loadChat.WorkspaceId.ToString(), loadChat.ChatId.ToString()); + + return Directory.Exists(chatPath); + } + + public static async Task StoreChat(ChatThread chat) + { + string chatDirectory; + if (chat.WorkspaceId == Guid.Empty) + chatDirectory = Path.Join(SettingsManager.DataDirectory, "tempChats", chat.ChatId.ToString()); + else + chatDirectory = Path.Join(SettingsManager.DataDirectory, "workspaces", chat.WorkspaceId.ToString(), chat.ChatId.ToString()); + + // Ensure the directory exists: + Directory.CreateDirectory(chatDirectory); + + // Save the chat name: + var chatNamePath = Path.Join(chatDirectory, "name"); + await File.WriteAllTextAsync(chatNamePath, chat.Name); + + // Save the thread as thread.json: + var chatPath = Path.Join(chatDirectory, "thread.json"); + await File.WriteAllTextAsync(chatPath, JsonSerializer.Serialize(chat, JSON_OPTIONS), Encoding.UTF8); + } + + public static async Task LoadChat(LoadChat loadChat) + { + var chatPath = loadChat.WorkspaceId == Guid.Empty + ? Path.Join(SettingsManager.DataDirectory, "tempChats", loadChat.ChatId.ToString()) + : Path.Join(SettingsManager.DataDirectory, "workspaces", loadChat.WorkspaceId.ToString(), loadChat.ChatId.ToString()); + + if(!Directory.Exists(chatPath)) + return null; + + try + { + var chatData = await File.ReadAllTextAsync(Path.Join(chatPath, "thread.json"), Encoding.UTF8); + var chat = JsonSerializer.Deserialize(chatData, JSON_OPTIONS); + return chat; + } + catch (Exception) + { + return null; + } + } + + public static async Task LoadWorkspaceName(Guid workspaceId) + { + if(workspaceId == Guid.Empty) + return string.Empty; + + var workspacePath = Path.Join(SettingsManager.DataDirectory, "workspaces", workspaceId.ToString()); + var workspaceNamePath = Path.Join(workspacePath, "name"); + return await File.ReadAllTextAsync(workspaceNamePath, Encoding.UTF8); + } +} \ No newline at end of file diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.9.20.md b/app/MindWork AI Studio/wwwroot/changelog/v0.9.20.md new file mode 100644 index 00000000..5aa8a084 --- /dev/null +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.20.md @@ -0,0 +1,2 @@ +# v0.9.20, build 195 (2024-11-16 xx:xx UTC) +- Fixed bugs related to storing and loading chats when the workspaces were docked to the left and were collapsed. \ No newline at end of file