Merge branch 'main' into feature/configurable-transcription-bitrate

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-09-18 17:37:31 +02:00
commit 28ff3ba625
26 files changed
+750 -571

No files matched your search

@@ -0,0 +1,29 @@
namespace AIStudio.Components;
/// <summary>
/// Why a data source is listed in the selection, but cannot be picked.
/// </summary>
/// <remarks>
/// A reason rather than a yes or no, because the row has to say something different for each of
/// them: one asks the user to wait, the other one asks them to act. Asking somebody to wait for
/// something which will never happen on its own is the worse of the two mistakes.
/// </remarks>
public enum DataSourceBlockReason
{
/// <summary>
/// Nothing is in the way, the data source can be picked.
/// </summary>
NONE,
/// <summary>
/// The index has to be built anew before this data source can answer a search. This passes by
/// itself, as soon as the background indexing has worked through the data source.
/// </summary>
AWAITING_REINDEX,
/// <summary>
/// The index cannot be read anymore. This does not pass by itself: only the user can start the
/// rebuild, because it sends every document to the embedding provider once more.
/// </summary>
NEEDS_REPAIR,
}
@@ -59,6 +59,18 @@
<MudTooltip Text="@T("Information")">
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Outlined.Info" OnClick="@(() => this.ShowInformation(context))"/>
</MudTooltip>
@*
Outside the two branches below on purpose: an index which cannot be read is a
matter of this machine, not of the configuration. Hiding the repair for a data
source the organization manages would leave it locked out of every chat with no
way back, and the selection points here for it.
*@
@if (this.CanRepairDataSource(context))
{
<MudTooltip Text="@T("Repair this data source by indexing it anew")">
<MudIconButton Color="Color.Warning" Icon="@Icons.Material.Filled.Build" OnClick="@(() => this.RepairDataSource(context))"/>
</MudTooltip>
}
@if (context.IsEnterpriseConfiguration)
{
<MudTooltip Text="@T("This data source is managed by your organization.")">
@@ -128,6 +128,25 @@ public partial class DataSourceManagement : MSGComponentBase
return this.SettingsManager.ConfigurationData.DataSources.Any(this.CanRefreshDataSource);
}
/// <remarks>
/// Shown only while the index of this data source cannot be read. The refresh button next to it
/// stays as it is: it would open the same store and fail the same way, but it is offered for
/// every internal data source regardless of state, and singling this one out would say more
/// about the state than that button ever has.
/// </remarks>
private bool CanRepairDataSource(IDataSource dataSource)
{
return this.DataSourceEmbeddingService.NeedsIndexRepair(dataSource);
}
private async Task RepairDataSource(IDataSource dataSource)
{
if (!this.CanRepairDataSource(dataSource))
return;
await DataSourceRepair.ConfirmAndRepairAsync(this.DialogService, this.DataSourceEmbeddingService, dataSource.Id, dataSource.Name);
}
private async Task AutomaticRefreshChanged(bool enabled)
{
this.SettingsManager.ConfigurationData.App.DataSourceIndexing.AutomaticRefresh = enabled;
@@ -92,7 +92,7 @@
<MudList T="IDataSource" Dense="@true" Class="data-source-rows" SelectionMode="@this.GetListSelectionMode()" @bind-SelectedValues:get="@this.selectedDataSources" @bind-SelectedValues:set="@this.SelectionChanged" Style="max-height: 14em;">
@foreach (var source in this.GetListedDataSources())
{
<DataSourceSelectionRow DataSource="@source" IsAwaitingReindex="@this.IsAwaitingReindex(source)"/>
<DataSourceSelectionRow DataSource="@source" BlockReason="@this.GetBlockReason(source)"/>
}
</MudList>
</MudField>
@@ -104,7 +104,7 @@
<MudList T="IDataSource" Dense="@true" Class="data-source-rows" SelectionMode="MudBlazor.SelectionMode.SingleSelection" SelectedValues="@this.selectedDataSources" Style="max-height: 14em;">
@foreach (var source in this.GetListedDataSources())
{
<DataSourceSelectionRow DataSource="@source" IsAwaitingReindex="@this.IsAwaitingReindex(source)"/>
<DataSourceSelectionRow DataSource="@source" BlockReason="@this.GetBlockReason(source)"/>
}
</MudList>
</ExpansionPanel>
@@ -51,6 +51,8 @@ public partial class DataSourceSelection : MSGComponentBase
private IReadOnlyList<IDataSource> availableDataSources = [];
private IReadOnlyList<IDataSource> dataSourcesAwaitingReindex = [];
private HashSet<string> dataSourceIdsAwaitingReindex = new(StringComparer.Ordinal);
private IReadOnlyList<IDataSource> dataSourcesNeedingRepair = [];
private HashSet<string> dataSourceIdsNeedingRepair = new(StringComparer.Ordinal);
private IReadOnlyCollection<IDataSource> selectedDataSources = [];
private bool aiBasedSourceSelection;
private bool aiBasedValidation;
@@ -230,15 +232,33 @@ public partial class DataSourceSelection : MSGComponentBase
this.availableDataSources = sources.AllowedDataSources;
this.dataSourcesAwaitingReindex = sources.DataSourcesAwaitingReindex;
this.dataSourceIdsAwaitingReindex = sources.DataSourcesAwaitingReindex.Select(source => source.Id).ToHashSet(StringComparer.Ordinal);
this.dataSourcesNeedingRepair = sources.DataSourcesNeedingRepair;
this.dataSourceIdsNeedingRepair = sources.DataSourcesNeedingRepair.Select(source => source.Id).ToHashSet(StringComparer.Ordinal);
this.selectedDataSources = sources.SelectedDataSources;
this.waitingForDataSources = false;
this.StateHasChanged();
}
private bool IsAwaitingReindex(IDataSource dataSource) => this.dataSourceIdsAwaitingReindex.Contains(dataSource.Id);
/// <summary>
/// Why a data source is listed but cannot be picked, if it cannot.
/// </summary>
/// <remarks>
/// The repair is asked about first. The service hands a data source to one of the two lists
/// only, but should that ever change, the reason the user can act on is the one worth showing.
/// </remarks>
private DataSourceBlockReason GetBlockReason(IDataSource dataSource)
{
if (this.dataSourceIdsNeedingRepair.Contains(dataSource.Id))
return DataSourceBlockReason.NEEDS_REPAIR;
if (this.dataSourceIdsAwaitingReindex.Contains(dataSource.Id))
return DataSourceBlockReason.AWAITING_REINDEX;
return DataSourceBlockReason.NONE;
}
/// <summary>
/// The data sources the list shows: the usable ones, plus the ones waiting for their index.
/// The data sources the list shows: the usable ones, plus the ones which cannot be searched.
/// </summary>
/// <remarks>
/// Kept in the order the data sources were configured in, rather than usable ones first. A row
@@ -247,11 +267,12 @@ public partial class DataSourceSelection : MSGComponentBase
/// </remarks>
private IReadOnlyList<IDataSource> GetListedDataSources()
{
if (this.dataSourcesAwaitingReindex.Count == 0)
if (this.dataSourcesAwaitingReindex.Count == 0 && this.dataSourcesNeedingRepair.Count == 0)
return this.availableDataSources;
var listedIds = this.availableDataSources.Select(source => source.Id).ToHashSet(StringComparer.Ordinal);
listedIds.UnionWith(this.dataSourceIdsAwaitingReindex);
listedIds.UnionWith(this.dataSourceIdsNeedingRepair);
return this.GetConfiguredDataSourcesSnapshot().Where(source => listedIds.Contains(source.Id)).ToList();
}
@@ -259,11 +280,11 @@ public partial class DataSourceSelection : MSGComponentBase
/// The preselected but unusable data sources the warning box lists.
/// </summary>
/// <remarks>
/// The ones waiting for their index are left out: they have a row of their own in the list
/// The ones which are only blocked are left out: they have a row of their own in the list
/// above, which says the same thing in the place the user is already looking.
/// </remarks>
private IReadOnlyList<IDataSource> GetUnavailablePreselectedDataSourcesToList() =>
this.GetUnavailablePreselectedDataSources().Where(source => !this.IsAwaitingReindex(source)).ToList();
this.GetUnavailablePreselectedDataSources().Where(source => this.GetBlockReason(source) is DataSourceBlockReason.NONE).ToList();
private async Task EnabledChanged(bool state)
{
@@ -2,8 +2,8 @@
@using AIStudio.Provider
@inherits MSGComponentBase
<MudTooltip Text="@T("This data source is waiting to be indexed again. Until that is finished, it cannot be searched.")" Disabled="@(!this.IsAwaitingReindex)" RootStyle="display: block;" Placement="Placement.Top">
<MudListItem Value="@this.DataSource" Disabled="@this.IsAwaitingReindex">
<MudTooltip Text="@this.GetBlockedTooltip()" Disabled="@(!this.IsBlocked)" RootStyle="display: block;" Placement="Placement.Top">
<MudListItem Value="@this.DataSource" Disabled="@this.IsBlocked">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1" Style="min-width: 0; width: 100%;">
<MudText Typo="Typo.body2" Style="min-width: 0; white-space: normal; overflow-wrap: anywhere;">
@this.DataSource.Name
@@ -11,9 +11,9 @@
@if (this.DataSource is IInternalDataSource internalSource)
{
<MudSpacer/>
@if (this.IsAwaitingReindex)
@if (this.IsBlocked)
{
<MudIcon Icon="@Icons.Material.Filled.HourglassTop" Size="Size.Small" Color="Color.Warning" Style="flex-shrink: 0;"/>
<MudIcon Icon="@this.GetBlockedIcon()" Size="Size.Small" Color="@this.GetBlockedIconColor()" Style="flex-shrink: 0;"/>
}
<MudTooltip Text="@internalSource.ConfidenceLevel.GetName()">
<MudIcon Icon="@Icons.Material.Filled.Security" Size="Size.Small" Class="confidence-icon" Style="@this.GetConfidenceIconStyle(internalSource)"/>
@@ -10,9 +10,13 @@ namespace AIStudio.Components;
/// can be used right now.
/// </summary>
/// <remarks>
/// A data source waiting for its index stays in the list instead of disappearing from it, but
/// cannot be picked, and the tooltip says why. The tool selection next to it in the chat answers
/// the same question the same way.
/// A data source which cannot be used stays in the list instead of disappearing from it, but cannot
/// be picked, and the tooltip says why. The tool selection next to it in the chat answers the same
/// question the same way.
///
/// Why it cannot be used decides what the row says and which icon it wears: an index being built
/// anew is a matter of waiting, an index which cannot be read is a matter of acting. Both are the
/// same row otherwise, which is why this is one component with a reason rather than two components.
///
/// The tooltip sits around the list item rather than inside it: a disabled item has its pointer
/// events switched off and would swallow the hover.
@@ -26,10 +30,31 @@ public partial class DataSourceSelectionRow : MSGComponentBase
public required IDataSource DataSource { get; set; }
/// <summary>
/// Whether this data source has to be indexed anew before it can answer a search.
/// Why this data source cannot be picked right now, if it cannot.
/// </summary>
[Parameter]
public bool IsAwaitingReindex { get; set; }
public DataSourceBlockReason BlockReason { get; set; } = DataSourceBlockReason.NONE;
private bool IsBlocked => this.BlockReason is not DataSourceBlockReason.NONE;
private string GetBlockedTooltip() => this.BlockReason switch
{
DataSourceBlockReason.AWAITING_REINDEX => T("This data source is waiting to be indexed again. Until that is finished, it cannot be searched."),
DataSourceBlockReason.NEEDS_REPAIR => T("The index of this data source cannot be read anymore. Open your data source settings with the gear icon above, then use the repair action there."),
_ => string.Empty,
};
private string GetBlockedIcon() => this.BlockReason switch
{
DataSourceBlockReason.NEEDS_REPAIR => Icons.Material.Filled.ReportProblem,
_ => Icons.Material.Filled.HourglassTop,
};
private Color GetBlockedIconColor() => this.BlockReason switch
{
DataSourceBlockReason.NEEDS_REPAIR => Color.Error,
_ => Color.Warning,
};
private string GetConfidenceIconStyle(IInternalDataSource dataSource) => $"{dataSource.ConfidenceLevel.SetColorStyle(this.SettingsManager)} flex-shrink: 0;";
}