Show the local RAG index on the information page

This commit is contained in:
Thorsten Sommer 2026-09-17 11:00:01 +02:00
parent 1e712f5f83
commit bcc1a3b5eb
Signed by untrusted user who does not match committer: tsommer
GPG Key ID: 371BBA77A02C0108
5 changed files with 224 additions and 54 deletions

View File

@ -9538,6 +9538,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2929232062"] = "Copies the confi
-- Copies the root certificate fingerprint to the clipboard
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2989678330"] = "Copies the root certificate fingerprint to the clipboard"
-- Local RAG index
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2990297806"] = "Local RAG index"
-- The toml crate parses the embedded prompt-injection phrase catalog when the runtime starts. This keeps the detection rules separate from the Rust implementation and easier to maintain.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2999154325"] = "The toml crate parses the embedded prompt-injection phrase catalog when the runtime starts. This keeps the detection rules separate from the Rust implementation and easier to maintain."
@ -9700,6 +9703,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T4058524336"] = "Allowed hosts: n
-- This library is used by the Rust runtime to read the current user's username, e.g. when an organization-managed ERI server uses the OS username for authentication.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T4060906280"] = "This library is used by the Rust runtime to read the current user's username, e.g. when an organization-managed ERI server uses the OS username for authentication."
-- Local RAG index version
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T4061134614"] = "Local RAG index version"
-- This library is used to create asynchronous streams in Rust. It allows us to work with streams of data that can be produced asynchronously, making it easier to handle events or data that arrive over time. We use this, e.g., to stream arbitrary data from the file system to the embedding system.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T4079152443"] = "This library is used to create asynchronous streams in Rust. It allows us to work with streams of data that can be produced asynchronously, making it easier to handle events or data that arrive over time. We use this, e.g., to stream arbitrary data from the file system to the embedding system."
@ -10699,33 +10705,96 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::INDEXSTORE::NOINDEXSTORECLIENT::T10
-- Starting
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::INDEXSTORE::NOINDEXSTORECLIENT::T1233211769"] = "Starting"
-- unknown
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::INDEXSTORE::NOINDEXSTORECLIENT::T2608177081"] = "unknown"
-- Native library path
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::INDEXSTORE::NOINDEXSTORECLIENT::T3089048804"] = "Native library path"
-- Unavailable
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::INDEXSTORE::NOINDEXSTORECLIENT::T3662391977"] = "Unavailable"
-- Process architecture
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::INDEXSTORE::NOINDEXSTORECLIENT::T563161633"] = "Process architecture"
-- Status
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::INDEXSTORE::NOINDEXSTORECLIENT::T6222351"] = "Status"
-- Native library
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::INDEXSTORE::NOINDEXSTORECLIENT::T634426545"] = "Native library"
-- no migration applied
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::INDEXSTORE::SQLITEINDEXSTORECLIENTIMPLEMENTATION::T1014567907"] = "no migration applied"
-- Database path
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::INDEXSTORE::SQLITEINDEXSTORECLIENTIMPLEMENTATION::T1100578143"] = "Database path"
-- Storage size
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::INDEXSTORE::SQLITEINDEXSTORECLIENTIMPLEMENTATION::T1230141403"] = "Storage size"
-- Full-text search (FTS5)
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::INDEXSTORE::SQLITEINDEXSTORECLIENTIMPLEMENTATION::T1396060361"] = "Full-text search (FTS5)"
-- Wrapper version
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::INDEXSTORE::SQLITEINDEXSTORECLIENTIMPLEMENTATION::T1469427584"] = "Wrapper version"
-- available
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::INDEXSTORE::SQLITEINDEXSTORECLIENTIMPLEMENTATION::T1727918744"] = "available"
-- not determined
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::INDEXSTORE::SQLITEINDEXSTORECLIENTIMPLEMENTATION::T1959939977"] = "not determined"
-- Indexed files
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::INDEXSTORE::SQLITEINDEXSTORECLIENTIMPLEMENTATION::T2235289713"] = "Indexed files"
-- {0} ({1} applied)
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::INDEXSTORE::SQLITEINDEXSTORECLIENTIMPLEMENTATION::T2286846332"] = "{0} ({1} applied)"
-- Search chunks
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::INDEXSTORE::SQLITEINDEXSTORECLIENTIMPLEMENTATION::T2333737457"] = "Search chunks"
-- Journal mode
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::INDEXSTORE::SQLITEINDEXSTORECLIENTIMPLEMENTATION::T2411639995"] = "Journal mode"
-- unknown
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::INDEXSTORE::SQLITEINDEXSTORECLIENTIMPLEMENTATION::T2608177081"] = "unknown"
-- Native library path
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::INDEXSTORE::SQLITEINDEXSTORECLIENTIMPLEMENTATION::T3089048804"] = "Native library path"
-- Database tables
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::INDEXSTORE::SQLITEINDEXSTORECLIENTIMPLEMENTATION::T3279078157"] = "Database tables"
-- Indexed data sources
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::INDEXSTORE::SQLITEINDEXSTORECLIENTIMPLEMENTATION::T3524534748"] = "Indexed data sources"
-- Reported version
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::INDEXSTORE::SQLITEINDEXSTORECLIENTIMPLEMENTATION::T3556099842"] = "Reported version"
-- not available
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::INDEXSTORE::SQLITEINDEXSTORECLIENTIMPLEMENTATION::T3574465749"] = "not available"
-- Permanently skipped files
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::INDEXSTORE::SQLITEINDEXSTORECLIENTIMPLEMENTATION::T3965853089"] = "Permanently skipped files"
-- Library source ID
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::INDEXSTORE::SQLITEINDEXSTORECLIENTIMPLEMENTATION::T4168112546"] = "Library source ID"
-- Schema version
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::INDEXSTORE::SQLITEINDEXSTORECLIENTIMPLEMENTATION::T424290830"] = "Schema version"
-- Process architecture
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::INDEXSTORE::SQLITEINDEXSTORECLIENTIMPLEMENTATION::T563161633"] = "Process architecture"
-- Native library
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::INDEXSTORE::SQLITEINDEXSTORECLIENTIMPLEMENTATION::T634426545"] = "Native library"
-- System architecture
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::INDEXSTORE::SQLITEINDEXSTORECLIENTIMPLEMENTATION::T818259255"] = "System architecture"
-- {0} ({1} applied, {2} pending)
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::INDEXSTORE::SQLITEINDEXSTORECLIENTIMPLEMENTATION::T983802795"] = "{0} ({1} applied, {2} pending)"
-- Reason
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::NODATABASECLIENT::T1093747001"] = "Reason"
@ -10750,6 +10819,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::VECTORSTORE::NOVECTORSTORECLIENT::T
-- Storage size
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::VECTORSTORE::QDRANTEDGECLIENTIMPLEMENTATION::T1230141403"] = "Storage size"
-- unknown
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::VECTORSTORE::QDRANTEDGECLIENTIMPLEMENTATION::T2608177081"] = "unknown"
-- Number of vector stores
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::VECTORSTORE::QDRANTEDGECLIENTIMPLEMENTATION::T2785004838"] = "Number of vector stores"
@ -10759,6 +10831,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::VECTORSTORE::QDRANTEDGECLIENTIMPLEM
-- Status
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::VECTORSTORE::QDRANTEDGECLIENTIMPLEMENTATION::T6222351"] = "Status"
-- Stored vectors
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::VECTORSTORE::QDRANTEDGECLIENTIMPLEMENTATION::T701689894"] = "Stored vectors"
-- Qdrant Edge is not available.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::VECTORSTORE::QDRANTEDGECLIENTIMPLEMENTATION::T744445696"] = "Qdrant Edge is not available."

View File

@ -22,25 +22,40 @@
<MudListItem T="string" Icon="@Icons.Material.Outlined.Build" Text="@this.VersionRust"/>
<MudListItem T="string" Icon="@Icons.Material.Outlined.Storage">
<MudText Typo="Typo.body1">
@this.VersionVectorStore
@this.DatabaseHeaderText(this.vectorStoreSection)
</MudText>
<MudCollapse Expanded="@this.showVectorStoreDetails">
<MudCollapse Expanded="@this.vectorStoreSection.ShowDetails">
<MudText Typo="Typo.body1" Class="mt-2 mb-2">
@foreach (var item in this.vectorStoreDisplayInfo)
@foreach (var item in this.BuildDatabaseInfoItems(this.vectorStoreSection))
{
<div style="display: flex; align-items: center; gap: 8px;">
<MudIcon Icon="@Icons.Material.Filled.ArrowRightAlt"/>
<span>@item.Label: @item.Value</span>
<MudCopyClipboardButton TooltipMessage="@(T("Copies the following to the clipboard")+": "+item.Value)" StringContent=@item.Value/>
</div>
<ConfigInfoRow Item="@item"/>
}
</MudText>
</MudCollapse>
<MudButton StartIcon="@(this.showVectorStoreDetails ? Icons.Material.Filled.ExpandLess : Icons.Material.Filled.ExpandMore)"
<MudButton StartIcon="@(this.vectorStoreSection.ShowDetails ? Icons.Material.Filled.ExpandLess : Icons.Material.Filled.ExpandMore)"
Size="Size.Small"
Variant="Variant.Text"
OnClick="@this.ToggleVectorStoreDetails">
@(this.showVectorStoreDetails ? T("Hide Details") : T("Show Details"))
OnClick="@(() => this.ToggleDatabaseDetails(this.vectorStoreSection))">
@(this.vectorStoreSection.ShowDetails ? T("Hide Details") : T("Show Details"))
</MudButton>
</MudListItem>
<MudListItem T="string" Icon="@Icons.Material.Outlined.ManageSearch">
<MudText Typo="Typo.body1">
@this.DatabaseHeaderText(this.indexStoreSection)
</MudText>
<MudCollapse Expanded="@this.indexStoreSection.ShowDetails">
<MudText Typo="Typo.body1" Class="mt-2 mb-2">
@foreach (var item in this.BuildDatabaseInfoItems(this.indexStoreSection))
{
<ConfigInfoRow Item="@item"/>
}
</MudText>
</MudCollapse>
<MudButton StartIcon="@(this.indexStoreSection.ShowDetails ? Icons.Material.Filled.ExpandLess : Icons.Material.Filled.ExpandMore)"
Size="Size.Small"
Variant="Variant.Text"
OnClick="@(() => this.ToggleDatabaseDetails(this.indexStoreSection))">
@(this.indexStoreSection.ShowDetails ? T("Hide Details") : T("Show Details"))
</MudButton>
</MudListItem>
<MudListItem T="string" Icon="@Icons.Material.Outlined.DocumentScanner" Text="@this.VersionPdfium"/>

View File

@ -4,7 +4,6 @@ using AIStudio.Components;
using AIStudio.Dialogs;
using AIStudio.Settings.DataModel;
using AIStudio.Tools.Databases;
using AIStudio.Tools.Databases.VectorStore;
using AIStudio.Tools.Metadata;
using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.Rust;
@ -98,20 +97,38 @@ public partial class Information : MSGComponentBase
private string VersionPdfium => $"{T("Used PDFium version")}: v{META_DATA_LIBRARIES.PdfiumVersion}";
private string VersionVectorStore
/// <summary>
/// Builds the headline of one database block.
/// </summary>
/// <remarks>
/// The vector store names the version from the build metadata, which is what this page always
/// showed. The index store names the one its client read from the running database instead:
/// there, which SQLite build actually got loaded is the whole point of showing it.
/// </remarks>
private string DatabaseHeaderText(DatabaseSection section)
{
get
var (nameLabel, versionLabel) = section.Role switch
{
if (this.vectorStore is null)
return $"{T("Vector store")}: {T("checking availability")}";
return this.vectorStore.Status switch
{
DatabaseClientStatus.AVAILABLE => $"{T("Vector store version")}: {this.vectorStore.Name} v{META_DATA_VECTOR_STORE.VectorStoreVersion}",
DatabaseClientStatus.STARTING => $"{T("Vector store")}: {this.vectorStore.Name} - {T("starting")}",
_ => $"{T("Vector store")}: {this.vectorStore.Name} - {T("not available")}"
DatabaseRole.VECTOR_STORE => (T("Vector store"), T("Vector store version")),
_ => (T("Local RAG index"), T("Local RAG index version"))
};
if (section.Client is null)
return $"{nameLabel}: {T("checking availability")}";
var version = section.Role switch
{
DatabaseRole.VECTOR_STORE => META_DATA_VECTOR_STORE.VectorStoreVersion,
_ => section.Client.Version
};
return section.Client.Status switch
{
DatabaseClientStatus.AVAILABLE when !string.IsNullOrWhiteSpace(version) => $"{versionLabel}: {section.Client.Name} v{version}",
DatabaseClientStatus.AVAILABLE => $"{nameLabel}: {section.Client.Name}",
DatabaseClientStatus.STARTING => $"{nameLabel}: {section.Client.Name} - {T("starting")}",
_ => $"{nameLabel}: {section.Client.Name} - {T("not available")}"
};
}
}
private string versionPandoc = TB("Determine Pandoc version, please wait...");
@ -121,7 +138,6 @@ public partial class Information : MSGComponentBase
private bool showEnterpriseConfigDetails;
private bool showVectorStoreDetails;
private bool showExternalHttpCustomRootCertificateDetails;
private List<IAvailablePlugin> configPlugins = [];
@ -141,10 +157,30 @@ public partial class Information : MSGComponentBase
private sealed record MandatoryInfoPanelData(string HeaderText, string PluginName, DataMandatoryInfo Info, DataMandatoryInfoAcceptance? Acceptance);
private sealed record VectorStoreDisplayInfo(string Label, string Value);
private readonly List<VectorStoreDisplayInfo> vectorStoreDisplayInfo = new();
private DatabaseClient? vectorStore;
private CancellationTokenSource? vectorStoreRefreshCancellationTokenSource;
private sealed record DatabaseDisplayInfo(string Label, string Value);
/// <summary>
/// Everything one database block on this page needs to show itself.
/// </summary>
/// <remarks>
/// Both blocks work the same way, so they share their state and their methods and differ only
/// in their role. Whoever adds a third database adds one field here, not another set of methods.
/// </remarks>
private sealed class DatabaseSection(DatabaseRole role)
{
public DatabaseRole Role => role;
public DatabaseClient? Client { get; set; }
public bool ShowDetails { get; set; }
public List<DatabaseDisplayInfo> DisplayInfo { get; } = [];
public CancellationTokenSource? RefreshCancellationTokenSource { get; set; }
}
private readonly DatabaseSection vectorStoreSection = new(DatabaseRole.VECTOR_STORE);
private readonly DatabaseSection indexStoreSection = new(DatabaseRole.INDEX_STORE);
private bool HasAnyActiveEnvironment => this.enterpriseEnvironments.Any(e => e.IsActive);
@ -191,9 +227,16 @@ public partial class Information : MSGComponentBase
this.updatePolicyMode = this.UpdatePolicy.CurrentMode;
this.logPaths = await this.RustService.GetLogPaths();
await this.RefreshVectorStoreInfo(CancellationToken.None);
if (this.vectorStore?.Status is DatabaseClientStatus.STARTING)
this.StartShortVectorStoreRefreshLoop();
// The index store goes first: the vector store asks it for the number of stored vectors,
// and this way that client is already cached when it does.
await this.RefreshDatabaseInfo(this.indexStoreSection, CancellationToken.None);
await this.RefreshDatabaseInfo(this.vectorStoreSection, CancellationToken.None);
if (this.indexStoreSection.Client?.Status is DatabaseClientStatus.STARTING)
this.StartShortDatabaseRefreshLoop(this.indexStoreSection);
if (this.vectorStoreSection.Client?.Status is DatabaseClientStatus.STARTING)
this.StartShortDatabaseRefreshLoop(this.vectorStoreSection);
// Determine the Pandoc version may take some time, so we start it here
// without waiting for the result:
@ -301,22 +344,31 @@ public partial class Information : MSGComponentBase
this.showExternalHttpCustomRootCertificateDetails = !this.showExternalHttpCustomRootCertificateDetails;
}
private void ToggleVectorStoreDetails()
private void ToggleDatabaseDetails(DatabaseSection section)
{
this.showVectorStoreDetails = !this.showVectorStoreDetails;
section.ShowDetails = !section.ShowDetails;
}
private async Task RefreshVectorStoreInfo(CancellationToken cancellationToken)
private IReadOnlyList<ConfigInfoRowItem> BuildDatabaseInfoItems(DatabaseSection section) => section.DisplayInfo
.Select((item, index) => new ConfigInfoRowItem(
Icons.Material.Filled.ArrowRightAlt,
$"{item.Label}: {item.Value}",
item.Value,
$"{T("Copies the following to the clipboard")}: {item.Value}",
index == 0 ? string.Empty : "margin-top: 4px;"))
.ToList();
private async Task RefreshDatabaseInfo(DatabaseSection section, CancellationToken cancellationToken)
{
var refreshedClient = await this.DatabaseClientProvider.RefreshClientAsync(DatabaseRole.VECTOR_STORE, cancellationToken);
this.vectorStore = refreshedClient;
this.vectorStoreDisplayInfo.Clear();
var refreshedClient = await this.DatabaseClientProvider.RefreshClientAsync(section.Role, cancellationToken);
section.Client = refreshedClient;
section.DisplayInfo.Clear();
try
{
await foreach (var (label, value) in refreshedClient.GetDisplayInfo().WithCancellation(cancellationToken))
{
this.vectorStoreDisplayInfo.Add(new VectorStoreDisplayInfo(label, value));
section.DisplayInfo.Add(new DatabaseDisplayInfo(label, value));
}
}
catch (OperationCanceledException)
@ -325,20 +377,24 @@ public partial class Information : MSGComponentBase
}
catch (Exception e)
{
this.vectorStore = new NoVectorStoreClient(refreshedClient.Name, e.Message, DatabaseClientStatus.STARTING);
await foreach (var (label, value) in this.vectorStore.GetDisplayInfo().WithCancellation(cancellationToken))
// Drop whatever came in before the failure: those lines would otherwise stand next to
// the status and reason of the stand-in client and read like current values.
section.DisplayInfo.Clear();
section.Client = DatabaseClientProvider.CreateUnavailableClient(section.Role, refreshedClient.Name, e.Message, DatabaseClientStatus.STARTING);
await foreach (var (label, value) in section.Client.GetDisplayInfo().WithCancellation(cancellationToken))
{
this.vectorStoreDisplayInfo.Add(new VectorStoreDisplayInfo(label, value));
section.DisplayInfo.Add(new DatabaseDisplayInfo(label, value));
}
}
}
private void StartShortVectorStoreRefreshLoop()
private void StartShortDatabaseRefreshLoop(DatabaseSection section)
{
this.vectorStoreRefreshCancellationTokenSource?.Cancel();
this.vectorStoreRefreshCancellationTokenSource?.Dispose();
this.vectorStoreRefreshCancellationTokenSource = new CancellationTokenSource();
var cancellationToken = this.vectorStoreRefreshCancellationTokenSource.Token;
section.RefreshCancellationTokenSource?.Cancel();
section.RefreshCancellationTokenSource?.Dispose();
section.RefreshCancellationTokenSource = new CancellationTokenSource();
var cancellationToken = section.RefreshCancellationTokenSource.Token;
Task.Run(async () =>
{
@ -350,11 +406,11 @@ public partial class Information : MSGComponentBase
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
await this.InvokeAsync(async () =>
{
await this.RefreshVectorStoreInfo(cancellationToken);
await this.RefreshDatabaseInfo(section, cancellationToken);
this.StateHasChanged();
});
if (this.vectorStore?.Status is not DatabaseClientStatus.STARTING)
if (section.Client?.Status is not DatabaseClientStatus.STARTING)
return;
}
catch (OperationCanceledException)
@ -366,7 +422,14 @@ public partial class Information : MSGComponentBase
return;
}
}
}, cancellationToken).Observe($"{nameof(Information)}: refreshing the vector store info");
}, cancellationToken).Observe($"{nameof(Information)}: refreshing the {section.Role} info");
}
private void CancelDatabaseRefreshLoop(DatabaseSection section)
{
section.RefreshCancellationTokenSource?.Cancel();
section.RefreshCancellationTokenSource?.Dispose();
section.RefreshCancellationTokenSource = null;
}
private IAvailablePlugin? FindManagedConfigurationPlugin(Guid configurationId)
@ -525,8 +588,8 @@ public partial class Information : MSGComponentBase
protected override void DisposeResources()
{
this.vectorStoreRefreshCancellationTokenSource?.Cancel();
this.vectorStoreRefreshCancellationTokenSource?.Dispose();
this.CancelDatabaseRefreshLoop(this.vectorStoreSection);
this.CancelDatabaseRefreshLoop(this.indexStoreSection);
base.DisposeResources();
}

View File

@ -69,6 +69,25 @@ public sealed class DatabaseClientProvider(RustService rustService, ILoggerFacto
client.Status);
}
/// <summary>
/// Builds the client which stands in for a database role that cannot serve right now.
/// </summary>
/// <remarks>
/// Callers outside this namespace get their stand-in from here instead of naming the concrete
/// type themselves, so a new role does not have to be spelled out in every one of them.
/// </remarks>
/// <param name="databaseRole">The role the stand-in has to fill.</param>
/// <param name="name">The name to show for the database.</param>
/// <param name="reason">Why the database is not available.</param>
/// <param name="status">Whether the database is starting or unavailable.</param>
/// <returns>A client which answers every operation without a database behind it.</returns>
public static DatabaseClient CreateUnavailableClient(DatabaseRole databaseRole, string name, string? reason, DatabaseClientStatus status) => databaseRole switch
{
DatabaseRole.VECTOR_STORE => new NoVectorStoreClient(name, reason, status),
DatabaseRole.INDEX_STORE => new NoIndexStoreClient(name, reason, status),
_ => new NoDatabaseClient(name, reason, status)
};
private DatabaseClient CacheIfAvailable(DatabaseRole databaseRole, DatabaseClient client)
{
if (!client.IsAvailable)

View File

@ -1,5 +1,3 @@
using System.Globalization;
using AIStudio.Tools.Databases.IndexStore;
using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.Rust;