Added the option to search for chats in all workspaces (#795)
Some checks failed
Build and Release / Determine run mode (push) Has been cancelled
Build and Release / Prepare & create release (push) Has been cancelled
Build and Release / Publish release (push) Has been cancelled
Build and Release / Read metadata (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Has been cancelled

This commit is contained in:
Thorsten Sommer 2026-06-04 19:34:52 +02:00 committed by GitHub
parent 9fc7eaff99
commit 0b41f5eb96
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 515 additions and 22 deletions

View File

@ -3196,15 +3196,27 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1469573738"] = "Delete"
-- Rename Workspace -- Rename Workspace
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1474303418"] = "Rename Workspace" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1474303418"] = "Rename Workspace"
-- Clear search
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1511254342"] = "Clear search"
-- Rename Chat -- Rename Chat
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T156144855"] = "Rename Chat" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T156144855"] = "Rename Chat"
-- Add workspace -- Add workspace
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1586005241"] = "Add workspace" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1586005241"] = "Add workspace"
-- Search chats
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1615077202"] = "Search chats"
-- Start a new chat in workspace '{0}'
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1840064668"] = "Start a new chat in workspace '{0}'"
-- Add chat -- Add chat
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1874060138"] = "Add chat" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1874060138"] = "Add chat"
-- No chats found
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1886517101"] = "No chats found"
-- Create Chat -- Create Chat
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1939006681"] = "Create Chat" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1939006681"] = "Create Chat"
@ -3250,6 +3262,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3355849203"] = "Rename"
-- Please enter a new or edit the name for your chat '{0}': -- Please enter a new or edit the name for your chat '{0}':
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3419791373"] = "Please enter a new or edit the name for your chat '{0}':" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3419791373"] = "Please enter a new or edit the name for your chat '{0}':"
-- Search chat contents
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3436662033"] = "Search chat contents"
-- Load Chat -- Load Chat
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3555709365"] = "Load Chat" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3555709365"] = "Load Chat"
@ -5956,6 +5971,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T878695986"] = "Learn about one co
-- Localization -- Localization
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T897888480"] = "Localization" UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T897888480"] = "Localization"
-- Hide search
UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T1281128983"] = "Hide search"
-- Reload your workspaces -- Reload your workspaces
UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T194629703"] = "Reload your workspaces" UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T194629703"] = "Reload your workspaces"
@ -5968,6 +5986,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T2813205227"] = "Open Chat Options"
-- Disappearing Chat -- Disappearing Chat
UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T3046519404"] = "Disappearing Chat" UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T3046519404"] = "Disappearing Chat"
-- Search your workspaces
UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T3059773282"] = "Search your workspaces"
-- Configure your workspaces -- Configure your workspaces
UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T3586092784"] = "Configure your workspaces" UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T3586092784"] = "Configure your workspaces"

View File

@ -11,6 +11,36 @@
} }
else else
{ {
@if (this.SearchVisible)
{
<MudStack Class="mx-3 mt-2 mb-1" Spacing="1" Style="position: sticky; top: 0; z-index: 2; background-color: var(--mud-palette-background);">
<MudStack Row="@true" AlignItems="AlignItems.Center" Wrap="Wrap.NoWrap" Spacing="1">
<MudTextField T="string"
Text="@this.searchText"
TextChanged="@this.OnSearchTextChanged"
Placeholder="@T("Search chats")"
Variant="Variant.Outlined"
Margin="Margin.Dense"
Immediate="@true"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Search"/>
<MudTooltip Text="@T("Clear search")" Placement="@WORKSPACE_ITEM_TOOLTIP_PLACEMENT">
<MudIconButton Icon="@Icons.Material.Filled.Clear" Size="Size.Medium" Color="Color.Inherit" Disabled="@(string.IsNullOrWhiteSpace(this.searchText))" OnClick="@this.ClearSearchAsync"/>
</MudTooltip>
</MudStack>
<MudStack Row="@true" AlignItems="AlignItems.Center" Wrap="Wrap.NoWrap" Spacing="1">
<MudSwitch T="bool" Value="@this.includeThreadContents" ValueChanged="@this.IncludeThreadContentsChanged" Color="Color.Primary">
@T("Search chat contents")
</MudSwitch>
@if (this.isSearchRunning)
{
<MudProgressCircular Size="Size.Small" Indeterminate="@true"/>
}
</MudStack>
</MudStack>
}
<MudTreeView T="ITreeItem" Items="@this.treeItems" SelectionMode="SelectionMode.SingleSelection" Hover="@true" ExpandOnClick="@true" Class="ma-3"> <MudTreeView T="ITreeItem" Items="@this.treeItems" SelectionMode="SelectionMode.SingleSelection" Hover="@true" ExpandOnClick="@true" Class="ma-3">
<ItemTemplate Context="item"> <ItemTemplate Context="item">
@switch (item.Value) @switch (item.Value)
@ -71,19 +101,22 @@ else
<MudText Style="justify-self: start;"> <MudText Style="justify-self: start;">
@treeItem.Text @treeItem.Text
</MudText> </MudText>
<div style="justify-self: end;"> @if (!this.HasSearchQuery)
<MudTooltip Text="@T("Add chat")" Placement="@WORKSPACE_ITEM_TOOLTIP_PLACEMENT"> {
<MudIconButton Icon="@Icons.Material.Filled.AddComment" Size="Size.Medium" Color="Color.Inherit" OnClick="@(() => this.AddChatAsync(treeItem.Path))"/> <div style="justify-self: end;">
</MudTooltip> <MudTooltip Text="@this.GetAddChatToWorkspaceTooltip(treeItem.Text)" Placement="@WORKSPACE_ITEM_TOOLTIP_PLACEMENT">
<MudIconButton Icon="@Icons.Material.Filled.AddComment" Size="Size.Medium" Color="Color.Inherit" OnClick="@(() => this.AddChatAsync(treeItem.Path))"/>
</MudTooltip>
<MudTooltip Text="@T("Rename")" Placement="@WORKSPACE_ITEM_TOOLTIP_PLACEMENT"> <MudTooltip Text="@T("Rename")" Placement="@WORKSPACE_ITEM_TOOLTIP_PLACEMENT">
<MudIconButton Icon="@Icons.Material.Filled.Edit" Size="Size.Medium" Color="Color.Inherit" OnClick="@(() => this.RenameWorkspaceAsync(treeItem.Path))"/> <MudIconButton Icon="@Icons.Material.Filled.Edit" Size="Size.Medium" Color="Color.Inherit" OnClick="@(() => this.RenameWorkspaceAsync(treeItem.Path))"/>
</MudTooltip> </MudTooltip>
<MudTooltip Text="@T("Delete")" Placement="@WORKSPACE_ITEM_TOOLTIP_PLACEMENT"> <MudTooltip Text="@T("Delete")" Placement="@WORKSPACE_ITEM_TOOLTIP_PLACEMENT">
<MudIconButton Icon="@Icons.Material.Filled.Delete" Size="Size.Medium" Color="Color.Error" OnClick="@(() => this.DeleteWorkspaceAsync(treeItem.Path))"/> <MudIconButton Icon="@Icons.Material.Filled.Delete" Size="Size.Medium" Color="Color.Error" OnClick="@(() => this.DeleteWorkspaceAsync(treeItem.Path))"/>
</MudTooltip> </MudTooltip>
</div> </div>
}
</div> </div>
</BodyContent> </BodyContent>
</MudTreeViewItem> </MudTreeViewItem>

View File

@ -32,14 +32,25 @@ public partial class Workspaces : MSGComponentBase
[Parameter] [Parameter]
public bool ExpandRootNodes { get; set; } = true; public bool ExpandRootNodes { get; set; } = true;
[Parameter]
public bool SearchVisible { get; set; }
[Parameter]
public EventCallback<bool> SearchVisibleChanged { get; set; }
private const Placement WORKSPACE_ITEM_TOOLTIP_PLACEMENT = Placement.Bottom; private const Placement WORKSPACE_ITEM_TOOLTIP_PLACEMENT = Placement.Bottom;
private readonly SemaphoreSlim treeLoadingSemaphore = new(1, 1); private readonly SemaphoreSlim treeLoadingSemaphore = new(1, 1);
private readonly List<TreeItemData<ITreeItem>> treeItems = []; private readonly List<TreeItemData<ITreeItem>> treeItems = [];
private readonly HashSet<Guid> loadingWorkspaceChatLists = []; private readonly HashSet<Guid> loadingWorkspaceChatLists = [];
private CancellationTokenSource? prefetchCancellationTokenSource; private CancellationTokenSource? prefetchCancellationTokenSource;
private CancellationTokenSource? searchCancellationTokenSource;
private bool isInitialLoading = true; private bool isInitialLoading = true;
private bool isDisposed; private bool isDisposed;
private bool includeThreadContents;
private bool isSearchRunning;
private string searchText = string.Empty;
private long searchRevision;
#region Overrides of ComponentBase #region Overrides of ComponentBase
@ -54,6 +65,7 @@ public partial class Workspaces : MSGComponentBase
private async Task LoadTreeItemsAsync(bool startPrefetch = true, bool forceReload = false) private async Task LoadTreeItemsAsync(bool startPrefetch = true, bool forceReload = false)
{ {
var shouldRunSearch = false;
await this.treeLoadingSemaphore.WaitAsync(); await this.treeLoadingSemaphore.WaitAsync();
try try
{ {
@ -64,7 +76,11 @@ public partial class Workspaces : MSGComponentBase
await WorkspaceBehaviour.ForceReloadWorkspaceTreeAsync(); await WorkspaceBehaviour.ForceReloadWorkspaceTreeAsync();
var snapshot = await WorkspaceBehaviour.GetOrLoadWorkspaceTreeShellAsync(); var snapshot = await WorkspaceBehaviour.GetOrLoadWorkspaceTreeShellAsync();
this.BuildTreeItems(snapshot); if (this.HasSearchQuery)
shouldRunSearch = true;
else
this.BuildTreeItems(snapshot);
this.isInitialLoading = false; this.isInitialLoading = false;
} }
finally finally
@ -72,12 +88,19 @@ public partial class Workspaces : MSGComponentBase
this.treeLoadingSemaphore.Release(); this.treeLoadingSemaphore.Release();
} }
await this.SafeStateHasChanged(); if (shouldRunSearch)
await this.SearchWorkspaceItemsAsync();
else
await this.SafeStateHasChanged();
if (startPrefetch) if (startPrefetch)
await this.StartPrefetchAsync(); await this.StartPrefetchAsync();
} }
private bool HasSearchQuery => this.SearchVisible && !string.IsNullOrWhiteSpace(this.searchText);
private string GetAddChatToWorkspaceTooltip(string workspaceName) => string.Format(T("Start a new chat in workspace '{0}'"), workspaceName);
private void BuildTreeItems(WorkspaceTreeCacheSnapshot snapshot) private void BuildTreeItems(WorkspaceTreeCacheSnapshot snapshot)
{ {
this.treeItems.Clear(); this.treeItems.Clear();
@ -219,6 +242,109 @@ public partial class Workspaces : MSGComponentBase
}; };
} }
private void BuildSearchTreeItems(WorkspaceSearchSnapshot snapshot)
{
this.treeItems.Clear();
if (snapshot.Workspaces.Count == 0 && snapshot.TemporaryChats.Count == 0)
{
this.treeItems.Add(new TreeItemData<ITreeItem>
{
Expandable = false,
Value = new TreeItemData
{
Depth = 0,
Branch = WorkspaceBranch.NONE,
Text = T("No chats found"),
Icon = Icons.Material.Filled.Search,
Expandable = false,
Path = "search_empty",
},
});
return;
}
if (snapshot.Workspaces.Count > 0)
{
var workspaceChildren = new List<TreeItemData<ITreeItem>>();
foreach (var workspace in snapshot.Workspaces)
workspaceChildren.Add(this.CreateSearchWorkspaceTreeItem(workspace));
this.treeItems.Add(new TreeItemData<ITreeItem>
{
Expanded = true,
Expandable = true,
Value = new TreeItemData
{
Depth = 0,
Branch = WorkspaceBranch.WORKSPACES,
Text = T("Workspaces"),
Icon = Icons.Material.Filled.Folder,
Expandable = true,
Path = "search_workspaces",
Children = workspaceChildren,
},
});
}
if (snapshot.Workspaces.Count > 0 && snapshot.TemporaryChats.Count > 0)
{
this.treeItems.Add(new TreeItemData<ITreeItem>
{
Expandable = false,
Value = new TreeDivider(),
});
}
if (snapshot.TemporaryChats.Count > 0)
{
var temporaryChatsChildren = new List<TreeItemData<ITreeItem>>();
foreach (var temporaryChat in snapshot.TemporaryChats)
temporaryChatsChildren.Add(this.CreateChatTreeItem(temporaryChat.Chat, WorkspaceBranch.TEMPORARY_CHATS, depth: 1, icon: Icons.Material.Filled.Timer));
this.treeItems.Add(new TreeItemData<ITreeItem>
{
Expanded = true,
Expandable = true,
Value = new TreeItemData
{
Depth = 0,
Branch = WorkspaceBranch.TEMPORARY_CHATS,
Text = T("Disappearing Chats"),
Icon = Icons.Material.Filled.Timer,
Expandable = true,
Path = "search_temp",
Children = temporaryChatsChildren,
},
});
}
}
private TreeItemData<ITreeItem> CreateSearchWorkspaceTreeItem(WorkspaceSearchWorkspace workspace)
{
var children = new List<TreeItemData<ITreeItem>>();
foreach (var chat in workspace.Chats)
children.Add(this.CreateChatTreeItem(chat.Chat, WorkspaceBranch.WORKSPACES, depth: 2, icon: Icons.Material.Filled.Chat));
return new TreeItemData<ITreeItem>
{
Expanded = true,
Expandable = true,
Value = new TreeItemData
{
Type = TreeItemType.WORKSPACE,
Depth = 1,
Branch = WorkspaceBranch.WORKSPACES,
Text = workspace.Name,
Icon = Icons.Material.Filled.Description,
Expandable = true,
Path = workspace.WorkspacePath,
Children = children,
},
};
}
private string GetTreeItemIcon(TreeItemData treeItem) private string GetTreeItemIcon(TreeItemData treeItem)
{ {
if (treeItem.Type is not TreeItemType.CHAT) if (treeItem.Type is not TreeItemType.CHAT)
@ -289,6 +415,106 @@ public partial class Workspaces : MSGComponentBase
} }
} }
public async Task ToggleSearchAsync()
{
var searchVisible = !this.SearchVisible;
this.SearchVisible = searchVisible;
await this.SearchVisibleChanged.InvokeAsync(searchVisible);
if (this.SearchVisible)
{
await this.SafeStateHasChanged();
return;
}
await this.CancelSearchAsync();
this.searchText = string.Empty;
this.isSearchRunning = false;
await this.LoadTreeItemsAsync(startPrefetch: false);
}
private async Task CancelSearchAsync()
{
this.searchRevision++;
if (this.searchCancellationTokenSource is not null)
{
await this.searchCancellationTokenSource.CancelAsync();
this.searchCancellationTokenSource.Dispose();
this.searchCancellationTokenSource = null;
}
}
private async Task OnSearchTextChanged(string value)
{
this.searchText = value;
if (string.IsNullOrWhiteSpace(this.searchText))
{
await this.CancelSearchAsync();
this.isSearchRunning = false;
await this.LoadTreeItemsAsync(startPrefetch: false);
return;
}
await this.SearchWorkspaceItemsAsync();
}
private async Task IncludeThreadContentsChanged(bool value)
{
this.includeThreadContents = value;
if (this.HasSearchQuery)
await this.SearchWorkspaceItemsAsync();
}
private async Task ClearSearchAsync()
{
this.searchText = string.Empty;
await this.CancelSearchAsync();
this.isSearchRunning = false;
await this.LoadTreeItemsAsync(startPrefetch: false);
}
private async Task SearchWorkspaceItemsAsync()
{
await this.CancelSearchAsync();
var text = this.searchText;
if (string.IsNullOrWhiteSpace(text))
return;
this.searchCancellationTokenSource = new CancellationTokenSource();
var token = this.searchCancellationTokenSource.Token;
var revision = ++this.searchRevision;
this.isSearchRunning = true;
await this.SafeStateHasChanged();
try
{
var snapshot = await WorkspaceBehaviour.SearchWorkspaceChatsAsync(text, this.includeThreadContents, token);
if (this.isDisposed || token.IsCancellationRequested || revision != this.searchRevision)
return;
this.BuildSearchTreeItems(snapshot);
}
catch (OperationCanceledException)
{
// Expected when the user keeps typing or hides the search row.
}
catch (Exception ex)
{
this.Logger.LogWarning(ex, "Failed while searching workspace chats.");
this.BuildSearchTreeItems(new([], []));
}
finally
{
if (revision == this.searchRevision)
{
this.isSearchRunning = false;
await this.SafeStateHasChanged();
}
}
}
private async Task OnWorkspaceClicked(TreeItemData treeItem) private async Task OnWorkspaceClicked(TreeItemData treeItem)
{ {
if (treeItem.Type is not TreeItemType.WORKSPACE) if (treeItem.Type is not TreeItemType.WORKSPACE)
@ -656,6 +882,9 @@ public partial class Workspaces : MSGComponentBase
this.prefetchCancellationTokenSource?.Cancel(); this.prefetchCancellationTokenSource?.Cancel();
this.prefetchCancellationTokenSource?.Dispose(); this.prefetchCancellationTokenSource?.Dispose();
this.prefetchCancellationTokenSource = null; this.prefetchCancellationTokenSource = null;
this.searchCancellationTokenSource?.Cancel();
this.searchCancellationTokenSource?.Dispose();
this.searchCancellationTokenSource = null;
base.DisposeResources(); base.DisposeResources();
} }

View File

@ -51,13 +51,16 @@
<MudTooltip Text="@T("Reload your workspaces")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT"> <MudTooltip Text="@T("Reload your workspaces")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
<MudIconButton Icon="@Icons.Material.Filled.Refresh" Size="Size.Medium" OnClick="@this.RefreshWorkspaces"/> <MudIconButton Icon="@Icons.Material.Filled.Refresh" Size="Size.Medium" OnClick="@this.RefreshWorkspaces"/>
</MudTooltip> </MudTooltip>
<MudTooltip Text="@this.WorkspaceSearchTooltip" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
<MudIconButton Icon="@this.WorkspaceSearchIcon" Size="Size.Medium" OnClick="@this.ToggleWorkspaceSearch"/>
</MudTooltip>
<MudTooltip Text="@T("Hide your workspaces")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT"> <MudTooltip Text="@T("Hide your workspaces")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
<MudIconButton Size="Size.Medium" Icon="@this.WorkspaceSidebarToggleIcon" Class="me-1" OnClick="@(() => this.ToggleWorkspaceSidebar())"/> <MudIconButton Size="Size.Medium" Icon="@this.WorkspaceSidebarToggleIcon" Class="me-1" OnClick="@(() => this.ToggleWorkspaceSidebar())"/>
</MudTooltip> </MudTooltip>
</MudStack> </MudStack>
</HeaderContent> </HeaderContent>
<ChildContent> <ChildContent>
<Workspaces @ref="this.workspaces" @bind-CurrentChatThread="@this.chatThread"/> <Workspaces @ref="this.workspaces" @bind-CurrentChatThread="@this.chatThread" @bind-SearchVisible="@this.workspaceSearchVisible"/>
</ChildContent> </ChildContent>
</InnerScrolling> </InnerScrolling>
} }
@ -77,10 +80,13 @@
<MudTooltip Text="@T("Reload your workspaces")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT"> <MudTooltip Text="@T("Reload your workspaces")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
<MudIconButton Icon="@Icons.Material.Filled.Refresh" Size="Size.Medium" OnClick="@this.RefreshWorkspaces"/> <MudIconButton Icon="@Icons.Material.Filled.Refresh" Size="Size.Medium" OnClick="@this.RefreshWorkspaces"/>
</MudTooltip> </MudTooltip>
<MudTooltip Text="@this.WorkspaceSearchTooltip" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
<MudIconButton Icon="@this.WorkspaceSearchIcon" Size="Size.Medium" OnClick="@this.ToggleWorkspaceSearch"/>
</MudTooltip>
</MudStack> </MudStack>
</HeaderContent> </HeaderContent>
<ChildContent> <ChildContent>
<Workspaces @ref="this.workspaces" @bind-CurrentChatThread="@this.chatThread"/> <Workspaces @ref="this.workspaces" @bind-CurrentChatThread="@this.chatThread" @bind-SearchVisible="@this.workspaceSearchVisible"/>
</ChildContent> </ChildContent>
</InnerScrolling> </InnerScrolling>
} }
@ -149,11 +155,14 @@
<MudTooltip Text="@T("Reload your workspaces")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT"> <MudTooltip Text="@T("Reload your workspaces")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
<MudIconButton Icon="@Icons.Material.Filled.Refresh" Size="Size.Medium" OnClick="@this.RefreshWorkspaces"/> <MudIconButton Icon="@Icons.Material.Filled.Refresh" Size="Size.Medium" OnClick="@this.RefreshWorkspaces"/>
</MudTooltip> </MudTooltip>
<MudTooltip Text="@this.WorkspaceSearchTooltip" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
<MudIconButton Icon="@this.WorkspaceSearchIcon" Size="Size.Medium" OnClick="@this.ToggleWorkspaceSearch"/>
</MudTooltip>
<MudIconButton Icon="@Icons.Material.Filled.Close" Color="Color.Error" Size="Size.Medium" OnClick="@(() => this.ToggleWorkspacesOverlay())"/> <MudIconButton Icon="@Icons.Material.Filled.Close" Color="Color.Error" Size="Size.Medium" OnClick="@(() => this.ToggleWorkspacesOverlay())"/>
</MudStack> </MudStack>
</MudDrawerHeader> </MudDrawerHeader>
<MudDrawerContainer Class="ml-6"> <MudDrawerContainer Class="ml-6">
<Workspaces @ref="this.workspaces" @bind-CurrentChatThread="@this.chatThread"/> <Workspaces @ref="this.workspaces" @bind-CurrentChatThread="@this.chatThread" @bind-SearchVisible="@this.workspaceSearchVisible"/>
</MudDrawerContainer> </MudDrawerContainer>
</MudDrawer> </MudDrawer>
} }

View File

@ -23,6 +23,7 @@ public partial class Chat : MSGComponentBase
private ChatThread? chatThread; private ChatThread? chatThread;
private AIStudio.Settings.Provider providerSettings = AIStudio.Settings.Provider.NONE; private AIStudio.Settings.Provider providerSettings = AIStudio.Settings.Provider.NONE;
private bool workspaceOverlayVisible; private bool workspaceOverlayVisible;
private bool workspaceSearchVisible;
private string currentWorkspaceName = string.Empty; private string currentWorkspaceName = string.Empty;
private Workspaces? workspaces; private Workspaces? workspaces;
private double splitterPosition = 30; private double splitterPosition = 30;
@ -51,6 +52,10 @@ public partial class Chat : MSGComponentBase
private string WorkspaceSidebarToggleIcon => this.SettingsManager.ConfigurationData.Workspace.IsSidebarVisible ? Icons.Material.Filled.ArrowCircleLeft : Icons.Material.Filled.ArrowCircleRight; private string WorkspaceSidebarToggleIcon => this.SettingsManager.ConfigurationData.Workspace.IsSidebarVisible ? Icons.Material.Filled.ArrowCircleLeft : Icons.Material.Filled.ArrowCircleRight;
private string WorkspaceSearchIcon => this.workspaceSearchVisible ? Icons.Material.Filled.SearchOff : Icons.Material.Filled.Search;
private string WorkspaceSearchTooltip => this.workspaceSearchVisible ? T("Hide search") : T("Search your workspaces");
private bool AreWorkspacesVisible => this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is not WorkspaceStorageBehavior.DISABLE_WORKSPACES private bool AreWorkspacesVisible => this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is not WorkspaceStorageBehavior.DISABLE_WORKSPACES
&& ((this.SettingsManager.ConfigurationData.Workspace.DisplayBehavior is WorkspaceDisplayBehavior.TOGGLE_SIDEBAR && this.SettingsManager.ConfigurationData.Workspace.IsSidebarVisible) && ((this.SettingsManager.ConfigurationData.Workspace.DisplayBehavior is WorkspaceDisplayBehavior.TOGGLE_SIDEBAR && this.SettingsManager.ConfigurationData.Workspace.IsSidebarVisible)
|| this.SettingsManager.ConfigurationData.Workspace.DisplayBehavior is WorkspaceDisplayBehavior.SIDEBAR_ALWAYS_VISIBLE); || this.SettingsManager.ConfigurationData.Workspace.DisplayBehavior is WorkspaceDisplayBehavior.SIDEBAR_ALWAYS_VISIBLE);
@ -107,6 +112,14 @@ public partial class Chat : MSGComponentBase
await this.workspaces.ForceRefreshFromDiskAsync(); await this.workspaces.ForceRefreshFromDiskAsync();
} }
private async Task ToggleWorkspaceSearch()
{
if (this.workspaces is null)
return;
await this.workspaces.ToggleSearchAsync();
}
#region Overrides of MSGComponentBase #region Overrides of MSGComponentBase
protected override void DisposeResources() protected override void DisposeResources()

View File

@ -3198,15 +3198,27 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1469573738"] = "Löschen"
-- Rename Workspace -- Rename Workspace
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1474303418"] = "Arbeitsbereich umbenennen" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1474303418"] = "Arbeitsbereich umbenennen"
-- Clear search
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1511254342"] = "Suche zurücksetzen"
-- Rename Chat -- Rename Chat
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T156144855"] = "Chat umbenennen" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T156144855"] = "Chat umbenennen"
-- Add workspace -- Add workspace
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1586005241"] = "Arbeitsbereich hinzufügen" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1586005241"] = "Arbeitsbereich hinzufügen"
-- Search chats
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1615077202"] = "Chats durchsuchen"
-- Start a new chat in workspace '{0}'
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1840064668"] = "Neuen Chat im Arbeitsbereich „{0}“ starten"
-- Add chat -- Add chat
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1874060138"] = "Chat hinzufügen" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1874060138"] = "Chat hinzufügen"
-- No chats found
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1886517101"] = "Keine Chats gefunden"
-- Create Chat -- Create Chat
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1939006681"] = "Chat erstellen" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1939006681"] = "Chat erstellen"
@ -3252,6 +3264,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3355849203"] = "Umbenennen"
-- Please enter a new or edit the name for your chat '{0}': -- Please enter a new or edit the name for your chat '{0}':
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3419791373"] = "Bitte geben Sie einen neuen Namen für ihren Chat „{0}“ ein oder bearbeiten Sie ihn:" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3419791373"] = "Bitte geben Sie einen neuen Namen für ihren Chat „{0}“ ein oder bearbeiten Sie ihn:"
-- Search chat contents
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3436662033"] = "Chat-Inhalte durchsuchen"
-- Load Chat -- Load Chat
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3555709365"] = "Chat laden" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3555709365"] = "Chat laden"
@ -5958,6 +5973,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T878695986"] = "Lerne jeden Tag ei
-- Localization -- Localization
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T897888480"] = "Lokalisierung" UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T897888480"] = "Lokalisierung"
-- Hide search
UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T1281128983"] = "Suche ausblenden"
-- Reload your workspaces -- Reload your workspaces
UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T194629703"] = "Arbeitsbereiche neu laden" UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T194629703"] = "Arbeitsbereiche neu laden"
@ -5970,6 +5988,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T2813205227"] = "Chat-Optionen öffnen"
-- Disappearing Chat -- Disappearing Chat
UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T3046519404"] = "Selbstlöschender Chat" UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T3046519404"] = "Selbstlöschender Chat"
-- Search your workspaces
UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T3059773282"] = "Arbeitsbereiche durchsuchen"
-- Configure your workspaces -- Configure your workspaces
UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T3586092784"] = "Konfigurieren Sie ihre Arbeitsbereiche" UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T3586092784"] = "Konfigurieren Sie ihre Arbeitsbereiche"
@ -6270,12 +6291,12 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2989678330"] = "Kopiert den Fing
-- Changelog -- Changelog
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3017574265"] = "Änderungsprotokoll" UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3017574265"] = "Änderungsprotokoll"
-- Vector store
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3046399223"] = "Vektordatenbank"
-- External HTTPS custom root certificates are configured but not active. -- External HTTPS custom root certificates are configured but not active.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3021325354"] = "Externe benutzerdefinierte Stammzertifikate sind konfiguriert, aber nicht aktiv." UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3021325354"] = "Externe benutzerdefinierte Stammzertifikate sind konfiguriert, aber nicht aktiv."
-- Vector store
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3046399223"] = "Vektordatenbank"
-- Enterprise configuration ID: -- Enterprise configuration ID:
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3092349641"] = "Unternehmenskonfigurations-ID:" UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3092349641"] = "Unternehmenskonfigurations-ID:"

View File

@ -3198,15 +3198,27 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1469573738"] = "Delete"
-- Rename Workspace -- Rename Workspace
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1474303418"] = "Rename Workspace" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1474303418"] = "Rename Workspace"
-- Clear search
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1511254342"] = "Clear search"
-- Rename Chat -- Rename Chat
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T156144855"] = "Rename Chat" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T156144855"] = "Rename Chat"
-- Add workspace -- Add workspace
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1586005241"] = "Add workspace" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1586005241"] = "Add workspace"
-- Search chats
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1615077202"] = "Search chats"
-- Start a new chat in workspace '{0}'
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1840064668"] = "Start a new chat in workspace '{0}'"
-- Add chat -- Add chat
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1874060138"] = "Add chat" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1874060138"] = "Add chat"
-- No chats found
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1886517101"] = "No chats found"
-- Create Chat -- Create Chat
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1939006681"] = "Create Chat" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1939006681"] = "Create Chat"
@ -3252,6 +3264,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3355849203"] = "Rename"
-- Please enter a new or edit the name for your chat '{0}': -- Please enter a new or edit the name for your chat '{0}':
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3419791373"] = "Please enter a new or edit the name for your chat '{0}':" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3419791373"] = "Please enter a new or edit the name for your chat '{0}':"
-- Search chat contents
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3436662033"] = "Search chat contents"
-- Load Chat -- Load Chat
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3555709365"] = "Load Chat" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3555709365"] = "Load Chat"
@ -5958,6 +5973,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T878695986"] = "Learn about one co
-- Localization -- Localization
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T897888480"] = "Localization" UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T897888480"] = "Localization"
-- Hide search
UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T1281128983"] = "Hide search"
-- Reload your workspaces -- Reload your workspaces
UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T194629703"] = "Reload your workspaces" UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T194629703"] = "Reload your workspaces"
@ -5970,6 +5988,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T2813205227"] = "Open Chat Options"
-- Disappearing Chat -- Disappearing Chat
UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T3046519404"] = "Disappearing Chat" UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T3046519404"] = "Disappearing Chat"
-- Search your workspaces
UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T3059773282"] = "Search your workspaces"
-- Configure your workspaces -- Configure your workspaces
UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T3586092784"] = "Configure your workspaces" UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T3586092784"] = "Configure your workspaces"
@ -6270,11 +6291,12 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2989678330"] = "Copies the root
-- Changelog -- Changelog
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3017574265"] = "Changelog" UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3017574265"] = "Changelog"
-- Vector store
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3046399223"] = "Vector store"
-- External HTTPS custom root certificates are configured but not active. -- External HTTPS custom root certificates are configured but not active.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3021325354"] = "External HTTPS custom root certificates are configured but not active." UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3021325354"] = "External HTTPS custom root certificates are configured but not active."
-- Vector store
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3046399223"] = "Vector store"
-- Enterprise configuration ID: -- Enterprise configuration ID:
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3092349641"] = "Enterprise configuration ID:" UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3092349641"] = "Enterprise configuration ID:"

View File

@ -230,6 +230,92 @@ public static class WorkspaceBehaviour
chats.RemoveAt(existingIndex); chats.RemoveAt(existingIndex);
} }
private static IReadOnlyList<string> ParseSearchTerms(string searchText) => searchText
.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.Where(term => !string.IsNullOrWhiteSpace(term))
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
private static IReadOnlyList<string> GetMissingTerms(string text, IReadOnlyList<string> terms) => terms
.Where(term => text.IndexOf(term, StringComparison.OrdinalIgnoreCase) < 0)
.ToList();
private static bool ChatThreadContainsTerms(ChatThread thread, IReadOnlyList<string> terms)
{
var matchedTerms = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var block in thread.Blocks)
{
if (block.HideFromUser || block.Content is not ContentText textContent || string.IsNullOrWhiteSpace(textContent.Text))
continue;
foreach (var term in terms)
if (textContent.Text.Contains(term, StringComparison.OrdinalIgnoreCase))
matchedTerms.Add(term);
if (matchedTerms.Count == terms.Count)
return true;
}
return false;
}
private static async Task<bool> ThreadContainsTermsAsync(WorkspaceTreeChat chat, IReadOnlyList<string> terms, CancellationToken token)
{
var (acquired, semaphore) = await TryAcquireChatSemaphoreAsync(chat.WorkspaceId, chat.ChatId, nameof(ThreadContainsTermsAsync));
if (!acquired)
return false;
try
{
var threadPath = Path.Join(chat.ChatPath, "thread.json");
if (!File.Exists(threadPath))
return false;
var chatData = await File.ReadAllTextAsync(threadPath, Encoding.UTF8, token);
token.ThrowIfCancellationRequested();
var thread = JsonSerializer.Deserialize<ChatThread>(chatData, JSON_OPTIONS);
return thread is not null && ChatThreadContainsTerms(thread, terms);
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
LOG.LogWarning(ex, "Failed to search chat thread for workspace '{WorkspaceId}', chat '{ChatId}'.", chat.WorkspaceId, chat.ChatId);
return false;
}
finally
{
semaphore.Release();
}
}
private static async Task<List<WorkspaceSearchResult>> SearchChatsAsync(IReadOnlyList<WorkspaceTreeChat> chats, IReadOnlyList<string> terms, bool includeThreadContents, CancellationToken token)
{
var results = new List<WorkspaceSearchResult>();
foreach (var chat in chats)
{
token.ThrowIfCancellationRequested();
var missingTerms = GetMissingTerms(chat.Name, terms);
if (missingTerms.Count == 0)
{
results.Add(new(chat, NameMatched: true, ThreadMatched: false));
continue;
}
if (!includeThreadContents)
continue;
var threadMatched = await ThreadContainsTermsAsync(chat, missingTerms, token);
if (threadMatched)
results.Add(new(chat, NameMatched: false, ThreadMatched: true));
}
return results;
}
private static async Task UpdateCacheAfterChatStored(Guid workspaceId, Guid chatId, string chatDirectory, string chatName, DateTimeOffset lastEditTime) private static async Task UpdateCacheAfterChatStored(Guid workspaceId, Guid chatId, string chatDirectory, string chatName, DateTimeOffset lastEditTime)
{ {
await WORKSPACE_TREE_CACHE_SEMAPHORE.WaitAsync(); await WORKSPACE_TREE_CACHE_SEMAPHORE.WaitAsync();
@ -348,6 +434,55 @@ public static class WorkspaceBehaviour
} }
} }
public static async Task<WorkspaceSearchSnapshot> SearchWorkspaceChatsAsync(string searchText, bool includeThreadContents, CancellationToken token = default)
{
var terms = ParseSearchTerms(searchText);
if (terms.Count == 0)
return new([], []);
List<WorkspaceTreeWorkspace> workspaces;
List<WorkspaceTreeChat> temporaryChats;
await WORKSPACE_TREE_CACHE_SEMAPHORE.WaitAsync(token);
try
{
await EnsureTreeShellLoadedCoreAsync();
workspaces = [];
foreach (var workspaceId in WORKSPACE_TREE_CACHE.WorkspaceOrder)
{
token.ThrowIfCancellationRequested();
if (!WORKSPACE_TREE_CACHE.Workspaces.TryGetValue(workspaceId, out var workspace))
continue;
if (!workspace.ChatsLoaded)
{
workspace.Chats = await ReadWorkspaceChatsCoreAsync(workspaceId, workspace.WorkspacePath);
workspace.ChatsLoaded = true;
}
workspaces.Add(ToPublicWorkspace(workspace));
}
temporaryChats = WORKSPACE_TREE_CACHE.TemporaryChats.Select(ToPublicChat).ToList();
}
finally
{
WORKSPACE_TREE_CACHE_SEMAPHORE.Release();
}
var matchingWorkspaces = new List<WorkspaceSearchWorkspace>();
foreach (var workspace in workspaces)
{
token.ThrowIfCancellationRequested();
var matchingChats = await SearchChatsAsync(workspace.Chats, terms, includeThreadContents, token);
if (matchingChats.Count > 0)
matchingWorkspaces.Add(new(workspace.WorkspaceId, workspace.WorkspacePath, workspace.Name, matchingChats));
}
var matchingTemporaryChats = await SearchChatsAsync(temporaryChats, terms, includeThreadContents, token);
return new(matchingWorkspaces, matchingTemporaryChats);
}
public static async Task TryPrefetchRemainingChatsAsync(Func<Guid, Task>? onWorkspaceUpdated = null, CancellationToken token = default) public static async Task TryPrefetchRemainingChatsAsync(Func<Guid, Task>? onWorkspaceUpdated = null, CancellationToken token = default)
{ {
while (true) while (true)

View File

@ -0,0 +1,3 @@
namespace AIStudio.Tools;
public readonly record struct WorkspaceSearchResult(WorkspaceTreeChat Chat, bool NameMatched, bool ThreadMatched);

View File

@ -0,0 +1,3 @@
namespace AIStudio.Tools;
public readonly record struct WorkspaceSearchSnapshot(IReadOnlyList<WorkspaceSearchWorkspace> Workspaces, IReadOnlyList<WorkspaceSearchResult> TemporaryChats);

View File

@ -0,0 +1,3 @@
namespace AIStudio.Tools;
public readonly record struct WorkspaceSearchWorkspace(Guid WorkspaceId, string WorkspacePath, string Name, IReadOnlyList<WorkspaceSearchResult> Chats);

View File

@ -4,6 +4,7 @@
- Added support for managed custom root certificate bundles and host allowlists for external HTTPS requests, helping Flatpak deployments connect to organization-internal services with private root CAs while keeping built-in cloud provider endpoints on system trust. - Added support for managed custom root certificate bundles and host allowlists for external HTTPS requests, helping Flatpak deployments connect to organization-internal services with private root CAs while keeping built-in cloud provider endpoints on system trust.
- Added support for reading enterprise policy files from a Flatpak provisioning extension. - Added support for reading enterprise policy files from a Flatpak provisioning extension.
- Added startup path and Linux package type details to the information page to make support easier. - Added startup path and Linux package type details to the information page to make support easier.
- Added the option to search for chats in all workspaces.
- Improved workspaces by adding a shortcut to start a new chat directly from each workspace row. - Improved workspaces by adding a shortcut to start a new chat directly from each workspace row.
- Improved the enterprise configuration details on the information page by showing where each configuration comes from and which configuration slot was used. - Improved the enterprise configuration details on the information page by showing where each configuration comes from and which configuration slot was used.
- Upgraded dependencies. - Upgraded dependencies.