2024-07-10 17:27:49 +00:00
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Text.Json;
|
|
|
|
|
using System.Text.Json.Serialization;
|
|
|
|
|
|
|
|
|
|
using AIStudio.Chat;
|
2024-07-11 06:54:46 +00:00
|
|
|
|
using AIStudio.Components.CommonDialogs;
|
2024-07-09 18:25:48 +00:00
|
|
|
|
using AIStudio.Settings;
|
|
|
|
|
|
|
|
|
|
using Microsoft.AspNetCore.Components;
|
2024-07-04 09:00:13 +00:00
|
|
|
|
|
2024-07-11 06:54:46 +00:00
|
|
|
|
using DialogOptions = AIStudio.Components.CommonDialogs.DialogOptions;
|
|
|
|
|
|
2024-07-04 09:00:13 +00:00
|
|
|
|
namespace AIStudio.Components.Blocks;
|
|
|
|
|
|
|
|
|
|
public partial class Workspaces : ComponentBase
|
|
|
|
|
{
|
2024-07-09 18:25:48 +00:00
|
|
|
|
[Inject]
|
|
|
|
|
private SettingsManager SettingsManager { get; set; } = null!;
|
|
|
|
|
|
2024-07-11 06:54:46 +00:00
|
|
|
|
[Inject]
|
|
|
|
|
private IDialogService DialogService { get; set; } = null!;
|
|
|
|
|
|
2024-07-09 18:25:48 +00:00
|
|
|
|
[Parameter]
|
|
|
|
|
public ChatThread? CurrentChatThread { get; set; }
|
2024-07-10 17:27:49 +00:00
|
|
|
|
|
|
|
|
|
[Parameter]
|
|
|
|
|
public EventCallback<ChatThread> CurrentChatThreadChanged { get; set; }
|
2024-07-09 18:25:48 +00:00
|
|
|
|
|
2024-07-10 17:27:49 +00:00
|
|
|
|
private static readonly JsonSerializerOptions JSON_OPTIONS = new()
|
|
|
|
|
{
|
|
|
|
|
WriteIndented = true,
|
|
|
|
|
AllowTrailingCommas = true,
|
|
|
|
|
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
|
|
|
|
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
|
|
|
|
|
PropertyNameCaseInsensitive = true,
|
|
|
|
|
Converters =
|
|
|
|
|
{
|
|
|
|
|
new JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseUpper),
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-07-11 06:54:03 +00:00
|
|
|
|
private readonly HashSet<ITreeItem> treeItems = new();
|
2024-07-09 18:25:48 +00:00
|
|
|
|
|
|
|
|
|
#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
|
|
|
|
|
//
|
|
|
|
|
|
2024-07-11 06:27:06 +00:00
|
|
|
|
await this.LoadTreeItems();
|
|
|
|
|
await base.OnInitializedAsync();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
private async Task LoadTreeItems()
|
|
|
|
|
{
|
|
|
|
|
this.treeItems.Clear();
|
2024-07-11 06:54:03 +00:00
|
|
|
|
this.treeItems.Add(new TreeItemData
|
2024-07-09 18:25:48 +00:00
|
|
|
|
{
|
|
|
|
|
Depth = 0,
|
|
|
|
|
Branch = WorkspaceBranch.WORKSPACES,
|
|
|
|
|
Text = "Workspaces",
|
|
|
|
|
Icon = Icons.Material.Filled.Folder,
|
|
|
|
|
Expandable = true,
|
2024-07-11 06:54:03 +00:00
|
|
|
|
Path = "root",
|
2024-07-11 06:27:06 +00:00
|
|
|
|
Children = await this.LoadWorkspaces(),
|
2024-07-09 18:25:48 +00:00
|
|
|
|
});
|
|
|
|
|
|
2024-07-11 06:54:03 +00:00
|
|
|
|
this.treeItems.Add(new TreeDivider());
|
|
|
|
|
this.treeItems.Add(new TreeItemData
|
2024-07-09 18:25:48 +00:00
|
|
|
|
{
|
|
|
|
|
Depth = 0,
|
|
|
|
|
Branch = WorkspaceBranch.TEMPORARY_CHATS,
|
|
|
|
|
Text = "Temporary chats",
|
|
|
|
|
Icon = Icons.Material.Filled.Timer,
|
|
|
|
|
Expandable = true,
|
2024-07-11 06:54:03 +00:00
|
|
|
|
Path = "temp",
|
2024-07-11 06:27:06 +00:00
|
|
|
|
Children = await this.LoadTemporaryChats(),
|
2024-07-09 18:25:48 +00:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-11 06:54:03 +00:00
|
|
|
|
private async Task<HashSet<ITreeItem>> LoadTemporaryChats()
|
2024-07-09 18:25:48 +00:00
|
|
|
|
{
|
2024-07-11 06:54:03 +00:00
|
|
|
|
var tempChildren = new HashSet<ITreeItem>();
|
2024-07-11 06:27:06 +00:00
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// 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))
|
2024-07-09 18:25:48 +00:00
|
|
|
|
{
|
2024-07-11 06:27:06 +00:00
|
|
|
|
// Read the `name` file:
|
|
|
|
|
var chatNamePath = Path.Join(tempChatDirPath, "name");
|
|
|
|
|
var chatName = await File.ReadAllTextAsync(chatNamePath, Encoding.UTF8);
|
2024-07-09 18:25:48 +00:00
|
|
|
|
|
2024-07-11 06:54:03 +00:00
|
|
|
|
tempChildren.Add(new TreeItemData
|
2024-07-11 06:27:06 +00:00
|
|
|
|
{
|
|
|
|
|
IsChat = true,
|
|
|
|
|
Depth = 1,
|
|
|
|
|
Branch = WorkspaceBranch.TEMPORARY_CHATS,
|
|
|
|
|
Text = chatName,
|
|
|
|
|
Icon = Icons.Material.Filled.Timer,
|
|
|
|
|
Expandable = false,
|
2024-07-11 06:54:03 +00:00
|
|
|
|
Path = tempChatDirPath,
|
2024-07-11 06:27:06 +00:00
|
|
|
|
});
|
|
|
|
|
}
|
2024-07-09 18:25:48 +00:00
|
|
|
|
|
2024-07-11 06:27:06 +00:00
|
|
|
|
return tempChildren;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-11 06:54:46 +00:00
|
|
|
|
private async Task<string> 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<HashSet<ITreeItem>> LoadWorkspaces()
|
2024-07-11 06:27:06 +00:00
|
|
|
|
{
|
2024-07-11 06:54:03 +00:00
|
|
|
|
var workspaces = new HashSet<ITreeItem>();
|
2024-07-11 06:27:06 +00:00
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Search for workspace folders in the data directory:
|
|
|
|
|
//
|
2024-07-10 17:27:49 +00:00
|
|
|
|
|
2024-07-11 06:27:06 +00:00
|
|
|
|
// Get the workspace root directory:
|
|
|
|
|
var workspaceDirectories = Path.Join(SettingsManager.DataDirectory, "workspaces");
|
2024-07-09 18:25:48 +00:00
|
|
|
|
|
2024-07-11 06:27:06 +00:00
|
|
|
|
// Ensure the directory exists:
|
|
|
|
|
Directory.CreateDirectory(workspaceDirectories);
|
2024-07-09 18:25:48 +00:00
|
|
|
|
|
2024-07-11 06:27:06 +00:00
|
|
|
|
// 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);
|
|
|
|
|
|
2024-07-11 06:54:03 +00:00
|
|
|
|
workspaces.Add(new TreeItemData
|
2024-07-11 06:27:06 +00:00
|
|
|
|
{
|
|
|
|
|
IsChat = false,
|
|
|
|
|
Depth = 1,
|
|
|
|
|
Branch = WorkspaceBranch.WORKSPACES,
|
|
|
|
|
Text = workspaceName,
|
|
|
|
|
Icon = Icons.Material.Filled.Description,
|
|
|
|
|
Expandable = true,
|
2024-07-11 06:54:03 +00:00
|
|
|
|
Path = workspaceDirPath,
|
2024-07-11 06:27:06 +00:00
|
|
|
|
Children = await this.LoadWorkspaceChats(workspaceDirPath),
|
|
|
|
|
});
|
|
|
|
|
}
|
2024-07-10 17:27:49 +00:00
|
|
|
|
|
2024-07-11 19:33:12 +00:00
|
|
|
|
workspaces.Add(new TreeButton(WorkspaceBranch.WORKSPACES, 1, "Add workspace",Icons.Material.Filled.Add, this.AddWorkspace));
|
2024-07-11 06:27:06 +00:00
|
|
|
|
return workspaces;
|
|
|
|
|
}
|
2024-07-09 18:25:48 +00:00
|
|
|
|
|
2024-07-11 06:54:03 +00:00
|
|
|
|
private async Task<HashSet<ITreeItem>> LoadWorkspaceChats(string workspacePath)
|
2024-07-11 06:27:06 +00:00
|
|
|
|
{
|
2024-07-11 06:54:03 +00:00
|
|
|
|
var workspaceChats = new HashSet<ITreeItem>();
|
2024-07-11 06:27:06 +00:00
|
|
|
|
|
|
|
|
|
// Enumerate the workspace directory:
|
|
|
|
|
foreach (var chatPath in Directory.EnumerateDirectories(workspacePath))
|
|
|
|
|
{
|
|
|
|
|
// Read the `name` file:
|
|
|
|
|
var chatNamePath = Path.Join(chatPath, "name");
|
|
|
|
|
var chatName = await File.ReadAllTextAsync(chatNamePath, Encoding.UTF8);
|
|
|
|
|
|
2024-07-11 06:54:03 +00:00
|
|
|
|
workspaceChats.Add(new TreeItemData
|
2024-07-11 06:27:06 +00:00
|
|
|
|
{
|
|
|
|
|
IsChat = true,
|
|
|
|
|
Depth = 2,
|
|
|
|
|
Branch = WorkspaceBranch.WORKSPACES,
|
|
|
|
|
Text = chatName,
|
|
|
|
|
Icon = Icons.Material.Filled.Chat,
|
|
|
|
|
Expandable = false,
|
2024-07-11 06:54:03 +00:00
|
|
|
|
Path = chatPath,
|
2024-07-11 06:27:06 +00:00
|
|
|
|
});
|
2024-07-10 17:27:49 +00:00
|
|
|
|
}
|
2024-07-11 06:27:06 +00:00
|
|
|
|
|
2024-07-11 19:33:12 +00:00
|
|
|
|
workspaceChats.Add(new TreeButton(WorkspaceBranch.WORKSPACES, 2, "Add chat",Icons.Material.Filled.Add, () => this.AddChat(workspacePath)));
|
2024-07-11 06:27:06 +00:00
|
|
|
|
return workspaceChats;
|
2024-07-10 17:27:49 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-07-11 06:54:03 +00:00
|
|
|
|
public async Task StoreChat(ChatThread chat)
|
2024-07-10 17:27:49 +00:00
|
|
|
|
{
|
|
|
|
|
string chatDirectory;
|
2024-07-11 06:54:03 +00:00
|
|
|
|
if (chat.WorkspaceId == Guid.Empty)
|
|
|
|
|
chatDirectory = Path.Join(SettingsManager.DataDirectory, "tempChats", chat.ChatId.ToString());
|
2024-07-10 17:27:49 +00:00
|
|
|
|
else
|
2024-07-11 06:54:03 +00:00
|
|
|
|
chatDirectory = Path.Join(SettingsManager.DataDirectory, "workspaces", chat.WorkspaceId.ToString(), chat.ChatId.ToString());
|
2024-07-10 17:27:49 +00:00
|
|
|
|
|
|
|
|
|
// Ensure the directory exists:
|
|
|
|
|
Directory.CreateDirectory(chatDirectory);
|
|
|
|
|
|
|
|
|
|
// Save the chat name:
|
|
|
|
|
var chatNamePath = Path.Join(chatDirectory, "name");
|
2024-07-11 06:54:03 +00:00
|
|
|
|
await File.WriteAllTextAsync(chatNamePath, chat.Name);
|
2024-07-10 17:27:49 +00:00
|
|
|
|
|
|
|
|
|
// Save the thread as thread.json:
|
|
|
|
|
var chatPath = Path.Join(chatDirectory, "thread.json");
|
2024-07-11 06:54:03 +00:00
|
|
|
|
await File.WriteAllTextAsync(chatPath, JsonSerializer.Serialize(chat, JSON_OPTIONS), Encoding.UTF8);
|
2024-07-11 06:27:06 +00:00
|
|
|
|
|
|
|
|
|
// Reload the tree items:
|
|
|
|
|
await this.LoadTreeItems();
|
|
|
|
|
this.StateHasChanged();
|
2024-07-10 17:27:49 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-07-11 06:54:46 +00:00
|
|
|
|
private async Task<ChatThread?> LoadChat(string? chatPath, bool switchToChat)
|
2024-07-10 17:27:49 +00:00
|
|
|
|
{
|
|
|
|
|
if(string.IsNullOrWhiteSpace(chatPath))
|
2024-07-11 06:54:46 +00:00
|
|
|
|
return null;
|
2024-07-10 17:27:49 +00:00
|
|
|
|
|
|
|
|
|
if(!Directory.Exists(chatPath))
|
2024-07-11 06:54:46 +00:00
|
|
|
|
return null;
|
2024-07-10 17:27:49 +00:00
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var chatData = await File.ReadAllTextAsync(Path.Join(chatPath, "thread.json"), Encoding.UTF8);
|
2024-07-11 06:54:46 +00:00
|
|
|
|
var chat = JsonSerializer.Deserialize<ChatThread>(chatData, JSON_OPTIONS);
|
|
|
|
|
if (switchToChat)
|
|
|
|
|
{
|
|
|
|
|
this.CurrentChatThread = chat;
|
|
|
|
|
await this.CurrentChatThreadChanged.InvokeAsync(this.CurrentChatThread);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return chat;
|
2024-07-10 17:27:49 +00:00
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine(e);
|
2024-07-11 06:54:46 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task DeleteChat(string? chatPath)
|
|
|
|
|
{
|
|
|
|
|
var chat = await this.LoadChat(chatPath, false);
|
|
|
|
|
if (chat is null)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
var workspaceName = await this.LoadWorkspaceName(chat.WorkspaceId);
|
|
|
|
|
var dialogParameters = new DialogParameters
|
|
|
|
|
{
|
|
|
|
|
{ "Message", (chat.WorkspaceId == Guid.Empty) switch
|
|
|
|
|
{
|
|
|
|
|
true => $"Are you sure you want to delete the temporary chat '{chat.Name}'?",
|
|
|
|
|
false => $"Are you sure you want to delete the chat '{chat.Name}' in the workspace '{workspaceName}'?",
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Delete Chat", dialogParameters, DialogOptions.FULLSCREEN);
|
|
|
|
|
var dialogResult = await dialogReference.Result;
|
|
|
|
|
if (dialogResult.Canceled)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
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());
|
|
|
|
|
|
|
|
|
|
Directory.Delete(chatDirectory, true);
|
|
|
|
|
await this.LoadTreeItems();
|
|
|
|
|
|
|
|
|
|
if(this.CurrentChatThread?.ChatId == chat.ChatId)
|
|
|
|
|
{
|
|
|
|
|
this.CurrentChatThread = null;
|
|
|
|
|
await this.CurrentChatThreadChanged.InvokeAsync(this.CurrentChatThread);
|
2024-07-09 18:25:48 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2024-07-11 11:03:07 +00:00
|
|
|
|
|
|
|
|
|
private async Task RenameChat(string? chatPath)
|
|
|
|
|
{
|
|
|
|
|
var chat = await this.LoadChat(chatPath, false);
|
|
|
|
|
if (chat is null)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
var dialogParameters = new DialogParameters
|
|
|
|
|
{
|
|
|
|
|
{ "Message", $"Please enter a new or edit the name for your chat '{chat.Name}':" },
|
|
|
|
|
{ "UserInput", chat.Name },
|
2024-07-11 19:33:12 +00:00
|
|
|
|
{ "ConfirmText", "Rename" },
|
2024-07-11 11:03:07 +00:00
|
|
|
|
};
|
|
|
|
|
|
2024-07-11 19:33:12 +00:00
|
|
|
|
var dialogReference = await this.DialogService.ShowAsync<SingleInputDialog>("Rename Chat", dialogParameters, DialogOptions.FULLSCREEN);
|
2024-07-11 11:03:07 +00:00
|
|
|
|
var dialogResult = await dialogReference.Result;
|
|
|
|
|
if (dialogResult.Canceled)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
chat.Name = (dialogResult.Data as string)!;
|
|
|
|
|
await this.StoreChat(chat);
|
|
|
|
|
await this.LoadTreeItems();
|
|
|
|
|
}
|
2024-07-11 19:33:12 +00:00
|
|
|
|
|
|
|
|
|
private async Task AddWorkspace()
|
|
|
|
|
{
|
|
|
|
|
var dialogParameters = new DialogParameters
|
|
|
|
|
{
|
|
|
|
|
{ "Message", "Please name your workspace:" },
|
|
|
|
|
{ "UserInput", string.Empty },
|
|
|
|
|
{ "ConfirmText", "Add workspace" },
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var dialogReference = await this.DialogService.ShowAsync<SingleInputDialog>("Add Workspace", dialogParameters, DialogOptions.FULLSCREEN);
|
|
|
|
|
var dialogResult = await dialogReference.Result;
|
|
|
|
|
if (dialogResult.Canceled)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
var workspaceId = Guid.NewGuid();
|
|
|
|
|
var workspacePath = Path.Join(SettingsManager.DataDirectory, "workspaces", workspaceId.ToString());
|
|
|
|
|
Directory.CreateDirectory(workspacePath);
|
|
|
|
|
|
|
|
|
|
var workspaceNamePath = Path.Join(workspacePath, "name");
|
|
|
|
|
await File.WriteAllTextAsync(workspaceNamePath, (dialogResult.Data as string)!, Encoding.UTF8);
|
|
|
|
|
|
|
|
|
|
await this.LoadTreeItems();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task AddChat(string workspacePath)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
2024-07-04 09:00:13 +00:00
|
|
|
|
}
|