using System.Text; using System.Text.Json; using System.Text.Json.Serialization; using AIStudio.Chat; using AIStudio.Settings; using Microsoft.AspNetCore.Components; namespace AIStudio.Components.Blocks; public partial class Workspaces : ComponentBase { [Inject] private SettingsManager SettingsManager { get; set; } = null!; [Parameter] public ChatThread? CurrentChatThread { get; set; } [Parameter] public EventCallback CurrentChatThreadChanged { get; set; } 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 HashSet> initialTreeItems = new(); #region Overrides of ComponentBase protected override async Task OnInitializedAsync() { // // Notice: In order to get the server-based loading to work, we need to respect the following rules: // - We must have initial tree items // - Those initial tree items cannot have children // - When assigning the tree items to the MudTreeViewItem component, we must set the Value property to the value of the item // this.initialTreeItems.Add(new TreeItemData { Depth = 0, Branch = WorkspaceBranch.WORKSPACES, Text = "Workspaces", Icon = Icons.Material.Filled.Folder, Expandable = true, Value = "root", }); this.initialTreeItems.Add(new TreeDivider()); this.initialTreeItems.Add(new TreeItemData { Depth = 0, Branch = WorkspaceBranch.TEMPORARY_CHATS, Text = "Temporary chats", Icon = Icons.Material.Filled.Timer, Expandable = true, Value = "temp", }); await base.OnInitializedAsync(); } #endregion private async Task>> LoadServerData(ITreeItem? parent) { switch (parent) { case TreeItemData item: switch (item.Branch) { case WorkspaceBranch.WORKSPACES: var workspaceChildren = new HashSet>(); if (item.Depth == 0) { // // Search for workspace folders in the data directory: // // Get the workspace root directory: var workspaceDirectories = Path.Join(SettingsManager.DataDirectory, "workspaces"); // Ensure the directory exists: Directory.CreateDirectory(workspaceDirectories); // Enumerate the workspace directories: foreach (var workspaceDirPath in Directory.EnumerateDirectories(workspaceDirectories)) { // Read the `name` file: var workspaceNamePath = Path.Join(workspaceDirPath, "name"); var workspaceName = await File.ReadAllTextAsync(workspaceNamePath, Encoding.UTF8); workspaceChildren.Add(new TreeItemData { IsChat = false, Depth = item.Depth + 1, Branch = WorkspaceBranch.WORKSPACES, Text = workspaceName, Icon = Icons.Material.Filled.Description, Expandable = true, Value = workspaceDirPath, }); } workspaceChildren.Add(new TreeButton(WorkspaceBranch.WORKSPACES, item.Depth + 1, "Add workspace",Icons.Material.Filled.Add)); } else if (item.Depth == 1) { // // Search for workspace chats in the workspace directory: // // Get the workspace directory: var workspaceDirPath = item.Value; if (workspaceDirPath is null) return []; // Enumerate the workspace directory: foreach (var chatPath in Directory.EnumerateDirectories(workspaceDirPath)) { // Read the `name` file: var chatNamePath = Path.Join(chatPath, "name"); var chatName = await File.ReadAllTextAsync(chatNamePath, Encoding.UTF8); workspaceChildren.Add(new TreeItemData { IsChat = true, Depth = item.Depth + 1, Branch = WorkspaceBranch.WORKSPACES, Text = chatName, Icon = Icons.Material.Filled.Chat, Expandable = false, Value = chatPath, }); } workspaceChildren.Add(new TreeButton(WorkspaceBranch.WORKSPACES, item.Depth + 1, "Add chat",Icons.Material.Filled.Add)); } return workspaceChildren; case WorkspaceBranch.TEMPORARY_CHATS: var tempChildren = new HashSet>(); // // Search for workspace folders in the data directory: // // Get the workspace root directory: var temporaryDirectories = Path.Join(SettingsManager.DataDirectory, "tempChats"); // Ensure the directory exists: Directory.CreateDirectory(temporaryDirectories); // Enumerate the workspace directories: foreach (var tempChatDirPath in Directory.EnumerateDirectories(temporaryDirectories)) { // Read the `name` file: var chatNamePath = Path.Join(tempChatDirPath, "name"); var chatName = await File.ReadAllTextAsync(chatNamePath, Encoding.UTF8); tempChildren.Add(new TreeItemData { IsChat = true, Depth = item.Depth + 1, Branch = WorkspaceBranch.TEMPORARY_CHATS, Text = chatName, Icon = Icons.Material.Filled.Timer, Expandable = false, Value = tempChatDirPath, }); } return tempChildren; } return []; default: return []; } } public async Task StoreChat(ChatThread thread) { string chatDirectory; if (thread.WorkspaceId == Guid.Empty) chatDirectory = Path.Join(SettingsManager.DataDirectory, "tempChats", thread.ChatId.ToString()); else chatDirectory = Path.Join(SettingsManager.DataDirectory, "workspaces", thread.WorkspaceId.ToString(), thread.ChatId.ToString()); // Ensure the directory exists: Directory.CreateDirectory(chatDirectory); // Save the chat name: var chatNamePath = Path.Join(chatDirectory, "name"); await File.WriteAllTextAsync(chatNamePath, thread.Name); // Save the thread as thread.json: var chatPath = Path.Join(chatDirectory, "thread.json"); await File.WriteAllTextAsync(chatPath, JsonSerializer.Serialize(thread, JSON_OPTIONS), Encoding.UTF8); } private async Task LoadChat(string? chatPath) { if(string.IsNullOrWhiteSpace(chatPath)) { Console.WriteLine("Error: chat path is empty."); return; } if(!Directory.Exists(chatPath)) { Console.WriteLine($"Error: chat not found: '{chatPath}'"); return; } try { var chatData = await File.ReadAllTextAsync(Path.Join(chatPath, "thread.json"), Encoding.UTF8); this.CurrentChatThread = JsonSerializer.Deserialize(chatData, JSON_OPTIONS); await this.CurrentChatThreadChanged.InvokeAsync(this.CurrentChatThread); Console.WriteLine($"Loaded chat: {this.CurrentChatThread?.Name}"); } catch (Exception e) { Console.WriteLine(e); Console.WriteLine(e.StackTrace); throw; } } }