2026-03-05 17:37:18 +00:00
using System.Text ;
2024-07-13 08:37:57 +00:00
using System.Text.Json ;
using AIStudio.Chat ;
2024-08-21 06:30:01 +00:00
using AIStudio.Dialogs ;
2024-07-13 08:37:57 +00:00
using AIStudio.Settings ;
using Microsoft.AspNetCore.Components ;
2024-08-21 06:30:01 +00:00
using DialogOptions = AIStudio . Dialogs . DialogOptions ;
2024-07-13 08:37:57 +00:00
2024-08-21 06:30:01 +00:00
namespace AIStudio.Components ;
2024-07-13 08:37:57 +00:00
2025-04-27 07:06:05 +00:00
public partial class Workspaces : MSGComponentBase
2024-07-13 08:37:57 +00:00
{
[Inject]
2024-08-01 19:53:28 +00:00
private IDialogService DialogService { get ; init ; } = null ! ;
2024-07-13 08:37:57 +00:00
2024-09-01 18:10:03 +00:00
[Inject]
private ILogger < Workspaces > Logger { get ; init ; } = null ! ;
2024-07-13 08:37:57 +00:00
[Parameter]
public ChatThread ? CurrentChatThread { get ; set ; }
[Parameter]
public EventCallback < ChatThread > CurrentChatThreadChanged { get ; set ; }
2024-11-02 21:53:02 +00:00
[Parameter]
public bool ExpandRootNodes { get ; set ; } = true ;
2024-07-13 08:37:57 +00:00
private const Placement WORKSPACE_ITEM_TOOLTIP_PLACEMENT = Placement . Bottom ;
2026-03-05 17:37:18 +00:00
private readonly SemaphoreSlim treeLoadingSemaphore = new ( 1 , 1 ) ;
private readonly List < TreeItemData < ITreeItem > > treeItems = [ ] ;
private readonly HashSet < Guid > loadingWorkspaceChatLists = [ ] ;
2024-07-24 13:17:45 +00:00
2026-03-05 17:37:18 +00:00
private CancellationTokenSource ? prefetchCancellationTokenSource ;
private bool isInitialLoading = true ;
private bool isDisposed ;
2024-07-13 08:37:57 +00:00
#region Overrides of ComponentBase
protected override async Task OnInitializedAsync ( )
{
2025-04-27 14:13:15 +00:00
await base . OnInitializedAsync ( ) ;
2026-03-05 17:37:18 +00:00
_ = this . LoadTreeItemsAsync ( startPrefetch : true ) ;
2024-07-13 08:37:57 +00:00
}
#endregion
2026-03-05 17:37:18 +00:00
private async Task LoadTreeItemsAsync ( bool startPrefetch = true , bool forceReload = false )
{
await this . treeLoadingSemaphore . WaitAsync ( ) ;
try
{
if ( this . isDisposed )
return ;
if ( forceReload )
await WorkspaceBehaviour . ForceReloadWorkspaceTreeAsync ( ) ;
var snapshot = await WorkspaceBehaviour . GetOrLoadWorkspaceTreeShellAsync ( ) ;
this . BuildTreeItems ( snapshot ) ;
this . isInitialLoading = false ;
}
finally
{
this . treeLoadingSemaphore . Release ( ) ;
}
await this . SafeStateHasChanged ( ) ;
if ( startPrefetch )
await this . StartPrefetchAsync ( ) ;
}
private void BuildTreeItems ( WorkspaceTreeCacheSnapshot snapshot )
2024-07-13 08:37:57 +00:00
{
2025-06-27 18:52:33 +00:00
this . treeItems . Clear ( ) ;
2026-03-05 17:37:18 +00:00
var workspaceChildren = new List < TreeItemData < ITreeItem > > ( ) ;
foreach ( var workspace in snapshot . Workspaces )
workspaceChildren . Add ( this . CreateWorkspaceTreeItem ( workspace ) ) ;
workspaceChildren . Add ( new TreeItemData < ITreeItem >
{
Expandable = false ,
Value = new TreeButton ( WorkspaceBranch . WORKSPACES , 1 , T ( "Add workspace" ) , Icons . Material . Filled . LibraryAdd , this . AddWorkspaceAsync ) ,
} ) ;
2025-06-27 18:52:33 +00:00
this . treeItems . Add ( new TreeItemData < ITreeItem >
2024-07-13 08:37:57 +00:00
{
2024-11-02 21:53:02 +00:00
Expanded = this . ExpandRootNodes ,
2024-07-13 08:37:57 +00:00
Expandable = true ,
2024-07-24 13:17:45 +00:00
Value = new TreeItemData
{
Depth = 0 ,
Branch = WorkspaceBranch . WORKSPACES ,
2025-04-27 07:06:05 +00:00
Text = T ( "Workspaces" ) ,
2024-07-24 13:17:45 +00:00
Icon = Icons . Material . Filled . Folder ,
Expandable = true ,
Path = "root" ,
2026-03-05 17:37:18 +00:00
Children = workspaceChildren ,
2024-07-24 13:17:45 +00:00
} ,
2025-06-27 18:52:33 +00:00
} ) ;
this . treeItems . Add ( new TreeItemData < ITreeItem >
{
Expandable = false ,
Value = new TreeDivider ( ) ,
} ) ;
2026-03-05 17:37:18 +00:00
var temporaryChatsChildren = new List < TreeItemData < ITreeItem > > ( ) ;
foreach ( var temporaryChat in snapshot . TemporaryChats . OrderByDescending ( x = > x . LastEditTime ) )
temporaryChatsChildren . Add ( CreateChatTreeItem ( temporaryChat , WorkspaceBranch . TEMPORARY_CHATS , depth : 1 , icon : Icons . Material . Filled . Timer ) ) ;
2025-06-27 18:52:33 +00:00
this . treeItems . Add ( new TreeItemData < ITreeItem >
2024-07-13 08:37:57 +00:00
{
2024-11-02 21:53:02 +00:00
Expanded = this . ExpandRootNodes ,
2024-07-13 08:37:57 +00:00
Expandable = true ,
2024-07-24 13:17:45 +00:00
Value = new TreeItemData
{
Depth = 0 ,
Branch = WorkspaceBranch . TEMPORARY_CHATS ,
2025-04-27 07:06:05 +00:00
Text = T ( "Disappearing Chats" ) ,
2024-07-24 13:17:45 +00:00
Icon = Icons . Material . Filled . Timer ,
Expandable = true ,
Path = "temp" ,
2026-03-05 17:37:18 +00:00
Children = temporaryChatsChildren ,
2024-07-24 13:17:45 +00:00
} ,
2024-07-13 08:37:57 +00:00
} ) ;
}
2026-03-05 17:37:18 +00:00
private TreeItemData < ITreeItem > CreateWorkspaceTreeItem ( WorkspaceTreeWorkspace workspace )
2024-07-13 08:37:57 +00:00
{
2026-03-05 17:37:18 +00:00
var children = new List < TreeItemData < ITreeItem > > ( ) ;
if ( workspace . ChatsLoaded )
{
foreach ( var workspaceChat in workspace . Chats . OrderByDescending ( x = > x . LastEditTime ) )
children . Add ( CreateChatTreeItem ( workspaceChat , WorkspaceBranch . WORKSPACES , depth : 2 , icon : Icons . Material . Filled . Chat ) ) ;
}
else if ( this . loadingWorkspaceChatLists . Contains ( workspace . WorkspaceId ) )
children . AddRange ( this . CreateLoadingRows ( workspace . WorkspacePath ) ) ;
children . Add ( new TreeItemData < ITreeItem >
{
Expandable = false ,
Value = new TreeButton ( WorkspaceBranch . WORKSPACES , 2 , T ( "Add chat" ) , Icons . Material . Filled . AddComment , ( ) = > this . AddChatAsync ( workspace . WorkspacePath ) ) ,
} ) ;
return new TreeItemData < ITreeItem >
{
Expandable = true ,
Value = new TreeItemData
2024-07-13 08:37:57 +00:00
{
2026-03-05 17:37:18 +00:00
Type = TreeItemType . WORKSPACE ,
2024-08-19 18:19:55 +00:00
Depth = 1 ,
2026-03-05 17:37:18 +00:00
Branch = WorkspaceBranch . WORKSPACES ,
Text = workspace . Name ,
Icon = Icons . Material . Filled . Description ,
Expandable = true ,
Path = workspace . WorkspacePath ,
Children = children ,
} ,
} ;
}
private IReadOnlyCollection < TreeItemData < ITreeItem > > CreateLoadingRows ( string workspacePath )
{
return
[
this . CreateLoadingTreeItem ( workspacePath , "loading_1" ) ,
this . CreateLoadingTreeItem ( workspacePath , "loading_2" ) ,
this . CreateLoadingTreeItem ( workspacePath , "loading_3" ) ,
] ;
}
private TreeItemData < ITreeItem > CreateLoadingTreeItem ( string workspacePath , string suffix )
{
return new TreeItemData < ITreeItem >
{
Expandable = false ,
Value = new TreeItemData
{
Type = TreeItemType . LOADING ,
Depth = 2 ,
Branch = WorkspaceBranch . WORKSPACES ,
Text = T ( "Loading chats..." ) ,
Icon = Icons . Material . Filled . HourglassTop ,
2024-07-13 08:37:57 +00:00
Expandable = false ,
2026-03-05 17:37:18 +00:00
Path = Path . Join ( workspacePath , suffix ) ,
} ,
} ;
}
private static TreeItemData < ITreeItem > CreateChatTreeItem ( WorkspaceTreeChat chat , WorkspaceBranch branch , int depth , string icon )
{
return new TreeItemData < ITreeItem >
2024-08-19 18:19:55 +00:00
{
Expandable = false ,
2026-03-05 17:37:18 +00:00
Value = new TreeItemData
{
Type = TreeItemType . CHAT ,
Depth = depth ,
Branch = branch ,
Text = chat . Name ,
Icon = icon ,
Expandable = false ,
Path = chat . ChatPath ,
LastEditTime = chat . LastEditTime ,
} ,
} ;
2024-07-13 08:37:57 +00:00
}
2026-03-05 17:37:18 +00:00
private async Task SafeStateHasChanged ( )
2024-07-13 08:37:57 +00:00
{
2026-03-05 17:37:18 +00:00
if ( this . isDisposed )
return ;
2024-07-13 08:37:57 +00:00
2026-03-05 17:37:18 +00:00
await this . InvokeAsync ( this . StateHasChanged ) ;
}
2024-07-13 08:37:57 +00:00
2026-03-05 17:37:18 +00:00
private async Task StartPrefetchAsync ( )
{
if ( this . prefetchCancellationTokenSource is not null )
{
await this . prefetchCancellationTokenSource . CancelAsync ( ) ;
this . prefetchCancellationTokenSource . Dispose ( ) ;
}
2024-07-13 08:37:57 +00:00
2026-03-05 17:37:18 +00:00
this . prefetchCancellationTokenSource = new CancellationTokenSource ( ) ;
await this . PrefetchWorkspaceChatsAsync ( this . prefetchCancellationTokenSource . Token ) ;
}
private async Task PrefetchWorkspaceChatsAsync ( CancellationToken cancellationToken )
{
try
2024-07-13 08:37:57 +00:00
{
2026-03-05 17:37:18 +00:00
await WorkspaceBehaviour . TryPrefetchRemainingChatsAsync ( async _ = >
2025-08-28 14:44:32 +00:00
{
2026-03-05 17:37:18 +00:00
if ( this . isDisposed | | cancellationToken . IsCancellationRequested )
return ;
await this . LoadTreeItemsAsync ( startPrefetch : false ) ;
} , cancellationToken ) ;
2024-07-13 08:37:57 +00:00
}
2026-03-05 17:37:18 +00:00
catch ( OperationCanceledException )
2024-07-24 13:17:45 +00:00
{
2026-03-05 17:37:18 +00:00
// Expected when the component is hidden or disposed.
}
catch ( Exception ex )
{
this . Logger . LogWarning ( ex , "Failed while prefetching workspace chats." ) ;
}
}
private async Task OnWorkspaceClicked ( TreeItemData treeItem )
{
if ( treeItem . Type is not TreeItemType . WORKSPACE )
return ;
if ( ! Guid . TryParse ( Path . GetFileName ( treeItem . Path ) , out var workspaceId ) )
return ;
await this . EnsureWorkspaceChatsLoadedAsync ( workspaceId ) ;
2024-07-13 08:37:57 +00:00
}
2026-03-05 17:37:18 +00:00
private async Task EnsureWorkspaceChatsLoadedAsync ( Guid workspaceId )
2024-07-13 08:37:57 +00:00
{
2026-03-05 17:37:18 +00:00
var snapshot = await WorkspaceBehaviour . GetOrLoadWorkspaceTreeShellAsync ( ) ;
var hasWorkspace = false ;
var chatsLoaded = false ;
2024-07-13 08:37:57 +00:00
2026-03-05 17:37:18 +00:00
foreach ( var workspace in snapshot . Workspaces )
2024-07-13 08:37:57 +00:00
{
2026-03-05 17:37:18 +00:00
if ( workspace . WorkspaceId ! = workspaceId )
continue ;
hasWorkspace = true ;
chatsLoaded = workspace . ChatsLoaded ;
break ;
2024-07-13 08:37:57 +00:00
}
2026-03-05 17:37:18 +00:00
if ( ! hasWorkspace | | chatsLoaded | | ! this . loadingWorkspaceChatLists . Add ( workspaceId ) )
return ;
await this . LoadTreeItemsAsync ( startPrefetch : false ) ;
try
2024-08-19 18:19:55 +00:00
{
2026-03-05 17:37:18 +00:00
await WorkspaceBehaviour . GetWorkspaceChatsAsync ( workspaceId ) ;
}
finally
2024-07-24 13:17:45 +00:00
{
2026-03-05 17:37:18 +00:00
this . loadingWorkspaceChatLists . Remove ( workspaceId ) ;
}
await this . LoadTreeItemsAsync ( startPrefetch : false ) ;
}
public async Task ForceRefreshFromDiskAsync ( )
{
if ( this . prefetchCancellationTokenSource is not null )
{
await this . prefetchCancellationTokenSource . CancelAsync ( ) ;
this . prefetchCancellationTokenSource . Dispose ( ) ;
this . prefetchCancellationTokenSource = null ;
}
2024-07-24 13:17:45 +00:00
2026-03-05 17:37:18 +00:00
this . loadingWorkspaceChatLists . Clear ( ) ;
this . isInitialLoading = true ;
await this . SafeStateHasChanged ( ) ;
await this . LoadTreeItemsAsync ( startPrefetch : true , forceReload : true ) ;
2024-07-13 08:37:57 +00:00
}
2026-03-05 17:37:18 +00:00
public async Task StoreChatAsync ( ChatThread chat , bool reloadTreeItems = false )
2024-07-13 08:37:57 +00:00
{
2026-03-05 17:37:18 +00:00
await WorkspaceBehaviour . StoreChatAsync ( chat ) ;
2024-07-13 08:37:57 +00:00
2026-03-05 17:37:18 +00:00
if ( reloadTreeItems )
this . loadingWorkspaceChatLists . Clear ( ) ;
2024-10-28 14:41:00 +00:00
2026-03-05 17:37:18 +00:00
await this . LoadTreeItemsAsync ( startPrefetch : false ) ;
2024-07-13 08:37:57 +00:00
}
2026-03-05 17:37:18 +00:00
private async Task < ChatThread ? > LoadChatAsync ( string? chatPath , bool switchToChat )
2024-07-13 08:37:57 +00:00
{
2026-03-05 17:37:18 +00:00
if ( string . IsNullOrWhiteSpace ( chatPath ) )
2024-07-13 08:37:57 +00:00
return null ;
2026-03-05 17:37:18 +00:00
if ( ! Directory . Exists ( chatPath ) )
2024-07-13 08:37:57 +00:00
return null ;
if ( switchToChat & & await MessageBus . INSTANCE . SendMessageUseFirstResult < bool , bool > ( this , Event . HAS_CHAT_UNSAVED_CHANGES ) )
{
2025-08-28 16:51:44 +00:00
var dialogParameters = new DialogParameters < ConfirmDialog >
2024-07-13 08:37:57 +00:00
{
2025-08-28 16:51:44 +00:00
{ x = > x . Message , T ( "Are you sure you want to load another chat? All unsaved changes will be lost." ) } ,
2024-07-13 08:37:57 +00:00
} ;
2026-03-05 17:37:18 +00:00
2025-04-27 07:06:05 +00:00
var dialogReference = await this . DialogService . ShowAsync < ConfirmDialog > ( T ( "Load Chat" ) , dialogParameters , DialogOptions . FULLSCREEN ) ;
2024-07-13 08:37:57 +00:00
var dialogResult = await dialogReference . Result ;
2024-07-24 13:17:45 +00:00
if ( dialogResult is null | | dialogResult . Canceled )
2024-07-13 08:37:57 +00:00
return null ;
}
try
{
var chatData = await File . ReadAllTextAsync ( Path . Join ( chatPath , "thread.json" ) , Encoding . UTF8 ) ;
2024-11-15 20:22:57 +00:00
var chat = JsonSerializer . Deserialize < ChatThread > ( chatData , WorkspaceBehaviour . JSON_OPTIONS ) ;
2024-07-13 08:37:57 +00:00
if ( switchToChat )
{
this . CurrentChatThread = chat ;
await this . CurrentChatThreadChanged . InvokeAsync ( this . CurrentChatThread ) ;
2025-01-02 12:16:47 +00:00
await MessageBus . INSTANCE . SendMessage < bool > ( this , Event . WORKSPACE_LOADED_CHAT_CHANGED ) ;
2024-07-13 08:37:57 +00:00
}
return chat ;
}
catch ( Exception e )
{
2024-09-01 18:10:03 +00:00
this . Logger . LogError ( $"Failed to load chat from '{chatPath}': {e.Message}" ) ;
2024-07-13 08:37:57 +00:00
}
return null ;
}
2026-03-05 17:37:18 +00:00
public async Task DeleteChatAsync ( string? chatPath , bool askForConfirmation = true , bool unloadChat = true )
2024-07-13 08:37:57 +00:00
{
2026-03-05 17:37:18 +00:00
var chat = await this . LoadChatAsync ( chatPath , false ) ;
2024-07-13 08:37:57 +00:00
if ( chat is null )
return ;
if ( askForConfirmation )
{
2026-03-05 17:37:18 +00:00
var workspaceName = await WorkspaceBehaviour . LoadWorkspaceNameAsync ( chat . WorkspaceId ) ;
2025-08-28 16:51:44 +00:00
var dialogParameters = new DialogParameters < ConfirmDialog >
2024-07-13 08:37:57 +00:00
{
{
2025-08-28 16:51:44 +00:00
x = > x . Message , ( chat . WorkspaceId = = Guid . Empty ) switch
2024-07-13 08:37:57 +00:00
{
2025-04-27 07:06:05 +00:00
true = > string . Format ( T ( "Are you sure you want to delete the temporary chat '{0}'?" ) , chat . Name ) ,
false = > string . Format ( T ( "Are you sure you want to delete the chat '{0}' in the workspace '{1}'?" ) , chat . Name , workspaceName ) ,
2024-07-13 08:37:57 +00:00
}
} ,
} ;
2025-04-27 07:06:05 +00:00
var dialogReference = await this . DialogService . ShowAsync < ConfirmDialog > ( T ( "Delete Chat" ) , dialogParameters , DialogOptions . FULLSCREEN ) ;
2024-07-13 08:37:57 +00:00
var dialogResult = await dialogReference . Result ;
2024-07-24 13:17:45 +00:00
if ( dialogResult is null | | dialogResult . Canceled )
2024-07-13 08:37:57 +00:00
return ;
}
2026-03-05 17:37:18 +00:00
await WorkspaceBehaviour . DeleteChatAsync ( this . DialogService , chat . WorkspaceId , chat . ChatId , askForConfirmation : false ) ;
await this . LoadTreeItemsAsync ( startPrefetch : false ) ;
2024-07-13 08:37:57 +00:00
2026-03-05 17:37:18 +00:00
if ( unloadChat & & this . CurrentChatThread ? . ChatId = = chat . ChatId )
2024-07-13 08:37:57 +00:00
{
this . CurrentChatThread = null ;
await this . CurrentChatThreadChanged . InvokeAsync ( this . CurrentChatThread ) ;
2025-01-02 12:16:47 +00:00
await MessageBus . INSTANCE . SendMessage < bool > ( this , Event . WORKSPACE_LOADED_CHAT_CHANGED ) ;
2024-07-13 08:37:57 +00:00
}
}
2026-03-05 17:37:18 +00:00
private async Task RenameChatAsync ( string? chatPath )
2024-07-13 08:37:57 +00:00
{
2026-03-05 17:37:18 +00:00
var chat = await this . LoadChatAsync ( chatPath , false ) ;
2024-07-13 08:37:57 +00:00
if ( chat is null )
return ;
2025-08-28 16:16:30 +00:00
var dialogParameters = new DialogParameters < SingleInputDialog >
2024-07-13 08:37:57 +00:00
{
2025-08-28 16:16:30 +00:00
{ x = > x . Message , string . Format ( T ( "Please enter a new or edit the name for your chat '{0}':" ) , chat . Name ) } ,
2025-08-28 16:31:50 +00:00
{ x = > x . InputHeaderText , T ( "Chat Name" ) } ,
2025-08-28 16:16:30 +00:00
{ x = > x . UserInput , chat . Name } ,
{ x = > x . ConfirmText , T ( "Rename" ) } ,
{ x = > x . ConfirmColor , Color . Info } ,
{ x = > x . AllowEmptyInput , false } ,
{ x = > x . EmptyInputErrorMessage , T ( "Please enter a chat name." ) } ,
2024-07-13 08:37:57 +00:00
} ;
2025-04-27 07:06:05 +00:00
var dialogReference = await this . DialogService . ShowAsync < SingleInputDialog > ( T ( "Rename Chat" ) , dialogParameters , DialogOptions . FULLSCREEN ) ;
2024-07-13 08:37:57 +00:00
var dialogResult = await dialogReference . Result ;
2024-07-24 13:17:45 +00:00
if ( dialogResult is null | | dialogResult . Canceled )
2024-07-13 08:37:57 +00:00
return ;
chat . Name = ( dialogResult . Data as string ) ! ;
2026-03-05 17:37:18 +00:00
if ( this . CurrentChatThread ? . ChatId = = chat . ChatId )
2025-05-17 18:35:51 +00:00
{
this . CurrentChatThread . Name = chat . Name ;
await this . CurrentChatThreadChanged . InvokeAsync ( this . CurrentChatThread ) ;
await MessageBus . INSTANCE . SendMessage < bool > ( this , Event . WORKSPACE_LOADED_CHAT_CHANGED ) ;
}
2026-03-05 17:37:18 +00:00
await WorkspaceBehaviour . StoreChatAsync ( chat ) ;
await this . LoadTreeItemsAsync ( startPrefetch : false ) ;
2024-07-13 08:37:57 +00:00
}
2026-03-05 17:37:18 +00:00
private async Task RenameWorkspaceAsync ( string? workspacePath )
2024-07-13 08:37:57 +00:00
{
2026-03-05 17:37:18 +00:00
if ( workspacePath is null )
2024-07-13 08:37:57 +00:00
return ;
var workspaceId = Guid . Parse ( Path . GetFileName ( workspacePath ) ) ;
2026-03-05 17:37:18 +00:00
var workspaceName = await WorkspaceBehaviour . LoadWorkspaceNameAsync ( workspaceId ) ;
2025-08-28 16:16:30 +00:00
var dialogParameters = new DialogParameters < SingleInputDialog >
2024-07-13 08:37:57 +00:00
{
2025-08-28 16:16:30 +00:00
{ x = > x . Message , string . Format ( T ( "Please enter a new or edit the name for your workspace '{0}':" ) , workspaceName ) } ,
2025-08-28 16:31:50 +00:00
{ x = > x . InputHeaderText , T ( "Workspace Name" ) } ,
2025-08-28 16:16:30 +00:00
{ x = > x . UserInput , workspaceName } ,
{ x = > x . ConfirmText , T ( "Rename" ) } ,
{ x = > x . ConfirmColor , Color . Info } ,
{ x = > x . AllowEmptyInput , false } ,
{ x = > x . EmptyInputErrorMessage , T ( "Please enter a workspace name." ) } ,
2024-07-13 08:37:57 +00:00
} ;
2025-04-27 07:06:05 +00:00
var dialogReference = await this . DialogService . ShowAsync < SingleInputDialog > ( T ( "Rename Workspace" ) , dialogParameters , DialogOptions . FULLSCREEN ) ;
2024-07-13 08:37:57 +00:00
var dialogResult = await dialogReference . Result ;
2024-07-24 13:17:45 +00:00
if ( dialogResult is null | | dialogResult . Canceled )
2024-07-13 08:37:57 +00:00
return ;
var alteredWorkspaceName = ( dialogResult . Data as string ) ! ;
var workspaceNamePath = Path . Join ( workspacePath , "name" ) ;
await File . WriteAllTextAsync ( workspaceNamePath , alteredWorkspaceName , Encoding . UTF8 ) ;
2026-03-05 17:37:18 +00:00
await WorkspaceBehaviour . UpdateWorkspaceNameInCacheAsync ( workspaceId , alteredWorkspaceName ) ;
await this . LoadTreeItemsAsync ( startPrefetch : false ) ;
2024-07-13 08:37:57 +00:00
}
2026-03-05 17:37:18 +00:00
private async Task AddWorkspaceAsync ( )
2024-07-13 08:37:57 +00:00
{
2025-08-28 16:16:30 +00:00
var dialogParameters = new DialogParameters < SingleInputDialog >
2024-07-13 08:37:57 +00:00
{
2025-08-28 16:16:30 +00:00
{ x = > x . Message , T ( "Please name your workspace:" ) } ,
2025-08-28 16:31:50 +00:00
{ x = > x . InputHeaderText , T ( "Workspace Name" ) } ,
2025-08-28 16:16:30 +00:00
{ x = > x . UserInput , string . Empty } ,
{ x = > x . ConfirmText , T ( "Add workspace" ) } ,
{ x = > x . ConfirmColor , Color . Info } ,
{ x = > x . AllowEmptyInput , false } ,
{ x = > x . EmptyInputErrorMessage , T ( "Please enter a workspace name." ) } ,
2024-07-13 08:37:57 +00:00
} ;
2025-04-27 07:06:05 +00:00
var dialogReference = await this . DialogService . ShowAsync < SingleInputDialog > ( T ( "Add Workspace" ) , dialogParameters , DialogOptions . FULLSCREEN ) ;
2024-07-13 08:37:57 +00:00
var dialogResult = await dialogReference . Result ;
2024-07-24 13:17:45 +00:00
if ( dialogResult is null | | dialogResult . Canceled )
2024-07-13 08:37:57 +00:00
return ;
var workspaceId = Guid . NewGuid ( ) ;
var workspacePath = Path . Join ( SettingsManager . DataDirectory , "workspaces" , workspaceId . ToString ( ) ) ;
Directory . CreateDirectory ( workspacePath ) ;
2026-03-05 17:37:18 +00:00
var workspaceName = ( dialogResult . Data as string ) ! ;
2024-07-13 08:37:57 +00:00
var workspaceNamePath = Path . Join ( workspacePath , "name" ) ;
2026-03-05 17:37:18 +00:00
await File . WriteAllTextAsync ( workspaceNamePath , workspaceName , Encoding . UTF8 ) ;
await WorkspaceBehaviour . AddWorkspaceToCacheAsync ( workspaceId , workspacePath , workspaceName ) ;
2024-07-13 08:37:57 +00:00
2026-03-05 17:37:18 +00:00
await this . LoadTreeItemsAsync ( startPrefetch : false ) ;
2024-07-13 08:37:57 +00:00
}
2024-10-28 14:41:00 +00:00
2026-03-05 17:37:18 +00:00
private async Task DeleteWorkspaceAsync ( string? workspacePath )
2024-07-13 08:37:57 +00:00
{
2026-03-05 17:37:18 +00:00
if ( workspacePath is null )
2024-07-13 08:37:57 +00:00
return ;
var workspaceId = Guid . Parse ( Path . GetFileName ( workspacePath ) ) ;
2026-03-05 17:37:18 +00:00
var workspaceName = await WorkspaceBehaviour . LoadWorkspaceNameAsync ( workspaceId ) ;
2024-07-13 08:37:57 +00:00
var chatCount = Directory . EnumerateDirectories ( workspacePath ) . Count ( ) ;
2025-08-28 16:51:44 +00:00
var dialogParameters = new DialogParameters < ConfirmDialog >
2024-07-13 08:37:57 +00:00
{
2025-08-28 16:51:44 +00:00
{ x = > x . Message , string . Format ( T ( "Are you sure you want to delete the workspace '{0}'? This will also delete {1} chat(s) in this workspace." ) , workspaceName , chatCount ) } ,
2024-07-13 08:37:57 +00:00
} ;
2025-04-27 07:06:05 +00:00
var dialogReference = await this . DialogService . ShowAsync < ConfirmDialog > ( T ( "Delete Workspace" ) , dialogParameters , DialogOptions . FULLSCREEN ) ;
2024-07-13 08:37:57 +00:00
var dialogResult = await dialogReference . Result ;
2024-07-24 13:17:45 +00:00
if ( dialogResult is null | | dialogResult . Canceled )
2024-07-13 08:37:57 +00:00
return ;
Directory . Delete ( workspacePath , true ) ;
2026-03-05 17:37:18 +00:00
await WorkspaceBehaviour . RemoveWorkspaceFromCacheAsync ( workspaceId ) ;
await this . LoadTreeItemsAsync ( startPrefetch : false ) ;
2024-07-13 08:37:57 +00:00
}
2026-03-05 17:37:18 +00:00
private async Task MoveChatAsync ( string? chatPath )
2024-07-13 08:37:57 +00:00
{
2026-03-05 17:37:18 +00:00
var chat = await this . LoadChatAsync ( chatPath , false ) ;
2024-07-13 08:37:57 +00:00
if ( chat is null )
return ;
2025-08-28 16:51:44 +00:00
var dialogParameters = new DialogParameters < WorkspaceSelectionDialog >
2024-07-13 08:37:57 +00:00
{
2025-08-28 16:51:44 +00:00
{ x = > x . Message , T ( "Please select the workspace where you want to move the chat to." ) } ,
{ x = > x . SelectedWorkspace , chat . WorkspaceId } ,
{ x = > x . ConfirmText , T ( "Move chat" ) } ,
2024-07-13 08:37:57 +00:00
} ;
2025-04-27 07:06:05 +00:00
var dialogReference = await this . DialogService . ShowAsync < WorkspaceSelectionDialog > ( T ( "Move Chat to Workspace" ) , dialogParameters , DialogOptions . FULLSCREEN ) ;
2024-07-13 08:37:57 +00:00
var dialogResult = await dialogReference . Result ;
2024-07-24 13:17:45 +00:00
if ( dialogResult is null | | dialogResult . Canceled )
2024-07-13 08:37:57 +00:00
return ;
2025-04-27 07:06:05 +00:00
var workspaceId = dialogResult . Data is Guid id ? id : Guid . Empty ;
2024-07-13 08:37:57 +00:00
if ( workspaceId = = Guid . Empty )
return ;
2026-03-05 17:37:18 +00:00
await WorkspaceBehaviour . DeleteChatAsync ( this . DialogService , chat . WorkspaceId , chat . ChatId , askForConfirmation : false ) ;
2024-07-13 08:37:57 +00:00
chat . WorkspaceId = workspaceId ;
if ( this . CurrentChatThread ? . ChatId = = chat . ChatId )
{
this . CurrentChatThread = chat ;
await this . CurrentChatThreadChanged . InvokeAsync ( this . CurrentChatThread ) ;
2025-01-02 12:16:47 +00:00
await MessageBus . INSTANCE . SendMessage < bool > ( this , Event . WORKSPACE_LOADED_CHAT_CHANGED ) ;
2024-07-13 08:37:57 +00:00
}
2026-03-05 17:37:18 +00:00
await WorkspaceBehaviour . StoreChatAsync ( chat ) ;
await this . LoadTreeItemsAsync ( startPrefetch : false ) ;
2024-07-13 08:37:57 +00:00
}
2026-03-05 17:37:18 +00:00
private async Task AddChatAsync ( string workspacePath )
2024-07-13 08:37:57 +00:00
{
if ( await MessageBus . INSTANCE . SendMessageUseFirstResult < bool , bool > ( this , Event . HAS_CHAT_UNSAVED_CHANGES ) )
{
2025-08-28 16:51:44 +00:00
var dialogParameters = new DialogParameters < ConfirmDialog >
2024-07-13 08:37:57 +00:00
{
2025-08-28 16:51:44 +00:00
{ x = > x . Message , T ( "Are you sure you want to create a another chat? All unsaved changes will be lost." ) } ,
2024-07-13 08:37:57 +00:00
} ;
2025-04-27 07:06:05 +00:00
var dialogReference = await this . DialogService . ShowAsync < ConfirmDialog > ( T ( "Create Chat" ) , dialogParameters , DialogOptions . FULLSCREEN ) ;
2024-07-13 08:37:57 +00:00
var dialogResult = await dialogReference . Result ;
2024-07-24 13:17:45 +00:00
if ( dialogResult is null | | dialogResult . Canceled )
2024-07-13 08:37:57 +00:00
return ;
}
var workspaceId = Guid . Parse ( Path . GetFileName ( workspacePath ) ) ;
var chat = new ChatThread
{
WorkspaceId = workspaceId ,
ChatId = Guid . NewGuid ( ) ,
Name = string . Empty ,
2024-11-15 20:22:57 +00:00
SystemPrompt = SystemPrompts . DEFAULT ,
2024-07-13 08:37:57 +00:00
Blocks = [ ] ,
} ;
var chatPath = Path . Join ( workspacePath , chat . ChatId . ToString ( ) ) ;
2026-03-05 17:37:18 +00:00
await WorkspaceBehaviour . StoreChatAsync ( chat ) ;
await this . LoadChatAsync ( chatPath , switchToChat : true ) ;
await this . LoadTreeItemsAsync ( startPrefetch : false ) ;
2024-07-13 08:37:57 +00:00
}
2025-04-27 14:13:15 +00:00
#region Overrides of MSGComponentBase
protected override async Task ProcessIncomingMessage < T > ( ComponentBase ? sendingComponent , Event triggeredEvent , T ? data ) where T : default
{
switch ( triggeredEvent )
{
case Event . PLUGINS_RELOADED :
2026-03-05 17:37:18 +00:00
await this . ForceRefreshFromDiskAsync ( ) ;
2025-04-27 14:13:15 +00:00
break ;
}
}
2026-03-05 17:37:18 +00:00
protected override void DisposeResources ( )
{
this . isDisposed = true ;
this . prefetchCancellationTokenSource ? . Cancel ( ) ;
this . prefetchCancellationTokenSource ? . Dispose ( ) ;
this . prefetchCancellationTokenSource = null ;
base . DisposeResources ( ) ;
}
2025-04-27 14:13:15 +00:00
#endregion
2024-07-13 08:37:57 +00:00
}