mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-05-13 09:14:12 +00:00
background embed
This commit is contained in:
parent
e3cb7e9734
commit
ac677c5ac7
@ -5719,6 +5719,9 @@ UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T1614176092"] = "Assistants"
|
||||
-- Update
|
||||
UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T1847791252"] = "Update"
|
||||
|
||||
-- Data sync
|
||||
UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T1903948824"] = "Data sync"
|
||||
|
||||
-- Leave Chat Page
|
||||
UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T2124749705"] = "Leave Chat Page"
|
||||
|
||||
@ -5737,6 +5740,9 @@ UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T2929332068"] = "Supporters"
|
||||
-- Writer
|
||||
UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T2979224202"] = "Writer"
|
||||
|
||||
-- Embeddings are waiting to be processed.
|
||||
UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T3439916590"] = "Embeddings are waiting to be processed."
|
||||
|
||||
-- Show details
|
||||
UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T3692372066"] = "Show details"
|
||||
|
||||
@ -5746,6 +5752,18 @@ UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T4256323669"] = "Information"
|
||||
-- Chat
|
||||
UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T578410699"] = "Chat"
|
||||
|
||||
-- Some embeddings failed. {0} file(s) need attention.
|
||||
UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T640352868"] = "Some embeddings failed. {0} file(s) need attention."
|
||||
|
||||
-- Some embeddings failed and need attention.
|
||||
UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T671981715"] = "Some embeddings failed and need attention."
|
||||
|
||||
-- Embeddings are running: {0} of {1} files are indexed.
|
||||
UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T714077986"] = "Embeddings are running: {0} of {1} files are indexed."
|
||||
|
||||
-- Embeddings
|
||||
UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T951463987"] = "Embeddings"
|
||||
|
||||
-- Get coding and debugging support from an LLM.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T1243850917"] = "Get coding and debugging support from an LLM."
|
||||
|
||||
@ -5908,6 +5926,30 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T582100343"] = "Chat in Workspace"
|
||||
-- Show your workspaces
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T733672375"] = "Show your workspaces"
|
||||
|
||||
-- AI Studio indexes local RAG data sources in the background. Finished files stay recorded so unchanged files can be skipped after a restart, while added or deleted files are detected during the next run.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::EMBEDDINGS::T1064986263"] = "AI Studio indexes local RAG data sources in the background. Finished files stay recorded so unchanged files can be skipped after a restart, while added or deleted files are detected during the next run."
|
||||
|
||||
-- Current file: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::EMBEDDINGS::T1166856644"] = "Current file: {0}"
|
||||
|
||||
-- Pending files: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::EMBEDDINGS::T2471889605"] = "Pending files: {0}"
|
||||
|
||||
-- {0} of {1} files are indexed.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::EMBEDDINGS::T2525374657"] = "{0} of {1} files are indexed."
|
||||
|
||||
-- Background embeddings
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::EMBEDDINGS::T2547971789"] = "Background embeddings"
|
||||
|
||||
-- Failed files: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::EMBEDDINGS::T309404893"] = "Failed files: {0}"
|
||||
|
||||
-- Indexed files: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::EMBEDDINGS::T3473125711"] = "Indexed files: {0}"
|
||||
|
||||
-- No local data source has been queued for embedding yet.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::EMBEDDINGS::T3774205531"] = "No local data source has been queued for embedding yet."
|
||||
|
||||
-- Unlike services like ChatGPT, which impose limits after intensive use, MindWork AI Studio offers unlimited usage through the providers API.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1009708591"] = "Unlike services like ChatGPT, which impose limits after intensive use, MindWork AI Studio offers unlimited usage through the providers API."
|
||||
|
||||
@ -6878,13 +6920,13 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::CONFIDENCESCHEMESEXTENSIONS::T3893997203"] = "
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::CONFIDENCESCHEMESEXTENSIONS::T4107860491"] = "Trust all LLM providers"
|
||||
|
||||
-- Reason
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::NODATABASECLIENT::T1093747001"] = "Reason"
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::NOEMBEDDINGSTORE::T1093747001"] = "Reason"
|
||||
|
||||
-- Unavailable
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::NODATABASECLIENT::T3662391977"] = "Unavailable"
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::NOEMBEDDINGSTORE::T3662391977"] = "Unavailable"
|
||||
|
||||
-- Status
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::NODATABASECLIENT::T6222351"] = "Status"
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::NOEMBEDDINGSTORE::T6222351"] = "Status"
|
||||
|
||||
-- Storage size
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::QDRANT::QDRANTCLIENTIMPLEMENTATION::T1230141403"] = "Storage size"
|
||||
@ -7528,6 +7570,27 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T378481461"] = "Source like p
|
||||
-- Document
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T4165204724"] = "Document"
|
||||
|
||||
-- Running
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T1160324588"] = "Running"
|
||||
|
||||
-- Idle
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T1168775091"] = "Idle"
|
||||
|
||||
-- Needs attention
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T1566837660"] = "Needs attention"
|
||||
|
||||
-- Queued
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T2655222900"] = "Queued"
|
||||
|
||||
-- Embedding
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T2838542994"] = "Embedding"
|
||||
|
||||
-- Completed
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T3968379570"] = "Completed"
|
||||
|
||||
-- Embeddings
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T951463987"] = "Embeddings"
|
||||
|
||||
-- Pandoc Installation
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PANDOCAVAILABILITYSERVICE::T185447014"] = "Pandoc Installation"
|
||||
|
||||
|
||||
@ -13,6 +13,9 @@ namespace AIStudio.Components.Settings;
|
||||
|
||||
public partial class SettingsPanelEmbeddings : SettingsPanelProviderBase
|
||||
{
|
||||
[Inject]
|
||||
private DataSourceEmbeddingService DataSourceEmbeddingService { get; init; } = null!;
|
||||
|
||||
[Parameter]
|
||||
public List<ConfigurationSelectData<string>> AvailableEmbeddingProviders { get; set; } = new();
|
||||
|
||||
@ -59,6 +62,7 @@ public partial class SettingsPanelEmbeddings : SettingsPanelProviderBase
|
||||
await this.UpdateEmbeddingProviders();
|
||||
|
||||
await this.SettingsManager.StoreSettings();
|
||||
await this.DataSourceEmbeddingService.QueueAllInternalDataSourcesAsync();
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
|
||||
@ -94,6 +98,7 @@ public partial class SettingsPanelEmbeddings : SettingsPanelProviderBase
|
||||
await this.UpdateEmbeddingProviders();
|
||||
|
||||
await this.SettingsManager.StoreSettings();
|
||||
await this.DataSourceEmbeddingService.QueueAllInternalDataSourcesAsync();
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
|
||||
@ -133,6 +138,7 @@ public partial class SettingsPanelEmbeddings : SettingsPanelProviderBase
|
||||
}
|
||||
|
||||
await this.UpdateEmbeddingProviders();
|
||||
await this.DataSourceEmbeddingService.QueueAllInternalDataSourcesAsync();
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
|
||||
|
||||
@ -1,11 +1,17 @@
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Settings.DataModel;
|
||||
using AIStudio.Tools.ERIClient.DataModel;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Dialogs.Settings;
|
||||
|
||||
public partial class SettingsDialogDataSources : SettingsDialogBase
|
||||
{
|
||||
[Inject]
|
||||
private DataSourceEmbeddingService DataSourceEmbeddingService { get; init; } = null!;
|
||||
|
||||
private string GetEmbeddingName(IDataSource dataSource)
|
||||
{
|
||||
if(dataSource is IInternalDataSource internalDataSource)
|
||||
@ -84,6 +90,7 @@ public partial class SettingsDialogDataSources : SettingsDialogBase
|
||||
|
||||
this.SettingsManager.ConfigurationData.DataSources.Add(addedDataSource);
|
||||
await this.SettingsManager.StoreSettings();
|
||||
await this.DataSourceEmbeddingService.QueueDataSourceAsync(addedDataSource);
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
|
||||
@ -146,6 +153,7 @@ public partial class SettingsDialogDataSources : SettingsDialogBase
|
||||
this.SettingsManager.ConfigurationData.DataSources[this.SettingsManager.ConfigurationData.DataSources.IndexOf(dataSource)] = editedDataSource;
|
||||
|
||||
await this.SettingsManager.StoreSettings();
|
||||
await this.DataSourceEmbeddingService.QueueDataSourceAsync(editedDataSource);
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
|
||||
@ -184,6 +192,7 @@ public partial class SettingsDialogDataSources : SettingsDialogBase
|
||||
{
|
||||
this.SettingsManager.ConfigurationData.DataSources.Remove(dataSource);
|
||||
await this.SettingsManager.StoreSettings();
|
||||
await this.DataSourceEmbeddingService.RemoveDataSourceAsync(dataSource);
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
}
|
||||
@ -220,4 +229,4 @@ public partial class SettingsDialogDataSources : SettingsDialogBase
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,7 +25,17 @@
|
||||
|
||||
<MudSpacer/>
|
||||
|
||||
<MudStack AlignItems="AlignItems.Center">
|
||||
@if (this.embeddingOverview.IsVisible)
|
||||
{
|
||||
<MudNavMenu>
|
||||
<MudTooltip Text="@this.EmbeddingNavigationTooltip" Placement="Placement.Right">
|
||||
<MudNavLink Href="@this.embeddingItem.Path" Match="@(this.embeddingItem.MatchAll ? NavLinkMatch.All : NavLinkMatch.Prefix)" Icon="@this.embeddingItem.Icon" Style="@this.embeddingItem.SetColorStyle(this.SettingsManager)" Class="custom-icon-color">
|
||||
@T("Data sync")
|
||||
</MudNavLink>
|
||||
</MudTooltip>
|
||||
</MudNavMenu>
|
||||
}
|
||||
<MudStack AlignItems="AlignItems.Center" Class="pb-2">
|
||||
<MudToolBar WrapContent="true">
|
||||
<VoiceRecorder />
|
||||
</MudToolBar>
|
||||
@ -53,8 +63,23 @@
|
||||
</MudNavMenu>
|
||||
|
||||
<MudSpacer/>
|
||||
|
||||
<MudStack AlignItems="AlignItems.Center">
|
||||
|
||||
@if (this.embeddingOverview.IsVisible)
|
||||
{
|
||||
<MudNavMenu>
|
||||
@if (this.SettingsManager.ConfigurationData.App.NavigationBehavior is NavBehavior.NEVER_EXPAND_USE_TOOLTIPS)
|
||||
{
|
||||
<MudTooltip Text="@this.EmbeddingNavigationTooltip" Placement="Placement.Right">
|
||||
<MudNavLink Href="@this.embeddingItem.Path" Match="@(this.embeddingItem.MatchAll ? NavLinkMatch.All : NavLinkMatch.Prefix)" Icon="@this.embeddingItem.Icon" Style="@this.embeddingItem.SetColorStyle(this.SettingsManager)" Class="custom-icon-color"/>
|
||||
</MudTooltip>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudNavLink Href="@this.embeddingItem.Path" Match="@(this.embeddingItem.MatchAll ? NavLinkMatch.All : NavLinkMatch.Prefix)" Icon="@this.embeddingItem.Icon" Style="@this.embeddingItem.SetColorStyle(this.SettingsManager)" Class="custom-icon-color"/>
|
||||
}
|
||||
</MudNavMenu>
|
||||
}
|
||||
<MudStack AlignItems="AlignItems.Center" Class="pb-2">
|
||||
<MudToolBar WrapContent="true">
|
||||
<VoiceRecorder />
|
||||
</MudToolBar>
|
||||
@ -79,4 +104,4 @@
|
||||
</MudLayout>
|
||||
</MudPaper>
|
||||
|
||||
<MudThemeProvider @ref="@this.themeProvider" Theme="@this.ColorTheme" IsDarkMode="@this.useDarkMode" />
|
||||
<MudThemeProvider @ref="@this.themeProvider" Theme="@this.ColorTheme" IsDarkMode="@this.useDarkMode" />
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using AIStudio.Dialogs;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Settings.DataModel;
|
||||
@ -38,6 +39,9 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
|
||||
|
||||
[Inject]
|
||||
private MudTheme ColorTheme { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private DataSourceEmbeddingService DataSourceEmbeddingService { get; init; } = null!;
|
||||
|
||||
private ILanguagePlugin Lang { get; set; } = PluginFactory.BaseLanguage;
|
||||
|
||||
@ -56,7 +60,9 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
|
||||
private bool startupCompleted;
|
||||
private readonly SemaphoreSlim mandatoryInfoDialogSemaphore = new(1, 1);
|
||||
|
||||
private DataSourceEmbeddingOverview embeddingOverview = new(false, true, DataSourceEmbeddingState.COMPLETED, 0, 0, 0, string.Empty);
|
||||
private IReadOnlyCollection<NavBarItem> navItems = [];
|
||||
private NavBarItem embeddingItem = new NavBarItem(string.Empty, string.Empty, string.Empty, string.Empty, string.Empty, false);
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
|
||||
@ -87,6 +93,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
|
||||
|
||||
// Ensure that all settings are loaded:
|
||||
await this.SettingsManager.LoadSettings();
|
||||
await this.DataSourceEmbeddingService.QueueAllInternalDataSourcesAsync();
|
||||
|
||||
// Register this component with the message bus:
|
||||
this.MessageBus.RegisterComponent(this);
|
||||
@ -94,7 +101,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
|
||||
[
|
||||
Event.UPDATE_AVAILABLE, Event.CONFIGURATION_CHANGED, Event.COLOR_THEME_CHANGED, Event.SHOW_ERROR,
|
||||
Event.SHOW_WARNING, Event.SHOW_SUCCESS, Event.STARTUP_PLUGIN_SYSTEM, Event.PLUGINS_RELOADED,
|
||||
Event.INSTALL_UPDATE, Event.STARTUP_COMPLETED,
|
||||
Event.INSTALL_UPDATE, Event.STARTUP_COMPLETED, Event.RAG_EMBEDDING_STATUS_CHANGED,
|
||||
]);
|
||||
|
||||
// Set the snackbar for the update service:
|
||||
@ -114,6 +121,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
|
||||
await this.themeProvider.WatchSystemDarkModeAsync(this.SystemeThemeChanged);
|
||||
await this.UpdateThemeConfiguration();
|
||||
this.LoadNavItems();
|
||||
this.LoadEmbeddingItem();
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
@ -175,6 +183,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
|
||||
|
||||
await this.UpdateThemeConfiguration();
|
||||
this.LoadNavItems();
|
||||
this.LoadEmbeddingItem();
|
||||
this.StateHasChanged();
|
||||
if (this.startupCompleted)
|
||||
_ = this.EnsureMandatoryInfosAcceptedAsync();
|
||||
@ -263,6 +272,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
|
||||
this.Lang = await this.SettingsManager.GetActiveLanguagePlugin();
|
||||
I18N.Init(this.Lang);
|
||||
this.LoadNavItems();
|
||||
this.LoadEmbeddingItem();
|
||||
|
||||
await this.InvokeAsync(this.StateHasChanged);
|
||||
if (this.startupCompleted)
|
||||
@ -273,6 +283,12 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
|
||||
this.startupCompleted = true;
|
||||
_ = this.EnsureMandatoryInfosAcceptedAsync();
|
||||
break;
|
||||
|
||||
case Event.RAG_EMBEDDING_STATUS_CHANGED:
|
||||
this.LoadNavItems();
|
||||
this.LoadEmbeddingItem();
|
||||
this.StateHasChanged();
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -306,6 +322,32 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
|
||||
yield return new(T("Settings"), Icons.Material.Filled.Settings, palette.DarkLighten, palette.GrayLight, Routes.SETTINGS, false);
|
||||
}
|
||||
|
||||
private void LoadEmbeddingItem()
|
||||
{
|
||||
this.embeddingOverview = this.DataSourceEmbeddingService.GetOverview();
|
||||
var palette = this.ColorTheme.GetCurrentPalette(this.SettingsManager);
|
||||
(string icon, string lightcolor, string darkcolor) embeddingIcon = this.embeddingOverview.State switch
|
||||
{
|
||||
(DataSourceEmbeddingState.FAILED) => (Icons.Material.Filled.Warning, palette.Error.Value, "#d32f2f"),
|
||||
(DataSourceEmbeddingState.QUEUED) => (Icons.Material.Filled.Sync, palette.Info.Value, "#1976d2"),
|
||||
_ => (Icons.Material.Filled.Sync, palette.Warning.Value, "#d29f00"),
|
||||
};
|
||||
this.embeddingItem = new NavBarItem(T("Embeddings"), embeddingIcon.icon, embeddingIcon.lightcolor, embeddingIcon.darkcolor, Routes.EMBEDDINGS, false);
|
||||
}
|
||||
|
||||
private string EmbeddingNavigationTooltip => this.embeddingOverview.State switch
|
||||
{
|
||||
DataSourceEmbeddingState.QUEUED => T("Embeddings are waiting to be processed."),
|
||||
DataSourceEmbeddingState.RUNNING => string.Format(
|
||||
T("Embeddings are running: {0} of {1} files are indexed."),
|
||||
this.embeddingOverview.IndexedFiles,
|
||||
this.embeddingOverview.TotalFiles),
|
||||
DataSourceEmbeddingState.FAILED => this.embeddingOverview.FailedFiles > 0
|
||||
? string.Format(T("Some embeddings failed. {0} file(s) need attention."), this.embeddingOverview.FailedFiles)
|
||||
: T("Some embeddings failed and need attention."),
|
||||
_ => this.embeddingOverview.NavLabel,
|
||||
};
|
||||
|
||||
private async Task ShowUpdateDialog()
|
||||
{
|
||||
if(this.currentUpdateResponse is null)
|
||||
|
||||
64
app/MindWork AI Studio/Pages/Embeddings.razor
Normal file
64
app/MindWork AI Studio/Pages/Embeddings.razor
Normal file
@ -0,0 +1,64 @@
|
||||
@attribute [Route(Routes.EMBEDDINGS)]
|
||||
@inherits MSGComponentBase
|
||||
|
||||
<MudStack Spacing="3">
|
||||
<MudPaper Class="pa-4 border-dashed border rounded-lg" Elevation="0">
|
||||
<MudText Typo="Typo.h5">@T("Background embeddings")</MudText>
|
||||
<MudText Typo="Typo.body1" Class="mt-2">
|
||||
@T("AI Studio indexes local RAG data sources in the background. Finished files stay recorded so unchanged files can be skipped after a restart, while added or deleted files are detected during the next run.")
|
||||
</MudText>
|
||||
<MudStack Row="true" Class="mt-3" Wrap="Wrap.Wrap" Spacing="2">
|
||||
<MudChip T="string" Color="Color.Success" Variant="Variant.Filled">@string.Format(T("Indexed files: {0}"), this.TotalIndexedFiles)</MudChip>
|
||||
<MudChip T="string" Color="Color.Info" Variant="Variant.Filled">@string.Format(T("Pending files: {0}"), this.TotalPendingFiles)</MudChip>
|
||||
<MudChip T="string" Color="Color.Error" Variant="Variant.Filled">@string.Format(T("Failed files: {0}"), this.TotalFailedFiles)</MudChip>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
|
||||
@if (this.Statuses.Count == 0)
|
||||
{
|
||||
<MudAlert Severity="Severity.Info" Variant="Variant.Outlined">
|
||||
@T("No local data source has been queued for embedding yet.")
|
||||
</MudAlert>
|
||||
}
|
||||
else
|
||||
{
|
||||
@foreach (var status in this.Statuses)
|
||||
{
|
||||
<MudPaper Class="pa-4 border rounded-lg" Elevation="0">
|
||||
<MudStack Spacing="2">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudText Typo="Typo.h6">@status.DataSourceName</MudText>
|
||||
<MudChip T="string" Color="@GetStatusColor(status)" Variant="Variant.Filled">@status.StateLabel</MudChip>
|
||||
</MudStack>
|
||||
|
||||
<MudProgressLinear Value="@status.ProgressPercent" Rounded="@true" Color="@GetStatusColor(status)" />
|
||||
|
||||
<MudText Typo="Typo.body2">
|
||||
@string.Format(T("{0} of {1} files are indexed."), status.IndexedFiles, status.TotalFiles)
|
||||
</MudText>
|
||||
|
||||
@if (status.FailedFiles > 0)
|
||||
{
|
||||
<MudText Typo="Typo.body2">
|
||||
@string.Format(T("Failed files: {0}"), status.FailedFiles)
|
||||
</MudText>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(status.CurrentFile))
|
||||
{
|
||||
<MudText Typo="Typo.body2">
|
||||
@string.Format(T("Current file: {0}"), status.CurrentFile)
|
||||
</MudText>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(status.LastError))
|
||||
{
|
||||
<MudAlert Severity="Severity.Warning" Variant="Variant.Text">
|
||||
@status.LastError
|
||||
</MudAlert>
|
||||
}
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
}
|
||||
}
|
||||
</MudStack>
|
||||
57
app/MindWork AI Studio/Pages/Embeddings.razor.cs
Normal file
57
app/MindWork AI Studio/Pages/Embeddings.razor.cs
Normal file
@ -0,0 +1,57 @@
|
||||
using AIStudio.Components;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Pages;
|
||||
|
||||
public partial class Embeddings : MSGComponentBase
|
||||
{
|
||||
[Inject]
|
||||
private DataSourceEmbeddingService DataSourceEmbeddingService { get; init; } = null!;
|
||||
|
||||
private IReadOnlyList<DataSourceEmbeddingStatus> Statuses { get; set; } = [];
|
||||
|
||||
private int TotalIndexedFiles => this.Statuses.Sum(status => status.IndexedFiles);
|
||||
|
||||
private int TotalPendingFiles => this.Statuses.Sum(status => Math.Max(0, status.TotalFiles - status.IndexedFiles - status.FailedFiles));
|
||||
|
||||
private int TotalFailedFiles => this.Statuses.Sum(status => status.FailedFiles);
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
this.ApplyFilters([], [ Event.RAG_EMBEDDING_STATUS_CHANGED, Event.CONFIGURATION_CHANGED ]);
|
||||
await base.OnInitializedAsync();
|
||||
this.ReloadStatuses();
|
||||
}
|
||||
|
||||
protected override Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
|
||||
{
|
||||
if (triggeredEvent is Event.RAG_EMBEDDING_STATUS_CHANGED or Event.CONFIGURATION_CHANGED)
|
||||
{
|
||||
this.ReloadStatuses();
|
||||
this.StateHasChanged();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void ReloadStatuses()
|
||||
{
|
||||
this.Statuses = this.DataSourceEmbeddingService
|
||||
.GetStatuses()
|
||||
.OrderBy(status => status.SortOrder)
|
||||
.ThenBy(status => status.DataSourceName, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static Color GetStatusColor(DataSourceEmbeddingStatus status) => status.State switch
|
||||
{
|
||||
DataSourceEmbeddingState.RUNNING => Color.Warning,
|
||||
DataSourceEmbeddingState.QUEUED => Color.Info,
|
||||
DataSourceEmbeddingState.FAILED => Color.Error,
|
||||
DataSourceEmbeddingState.COMPLETED when status.FailedFiles > 0 => Color.Warning,
|
||||
DataSourceEmbeddingState.COMPLETED => Color.Success,
|
||||
_ => Color.Default,
|
||||
};
|
||||
}
|
||||
@ -29,7 +29,7 @@ public partial class Information : MSGComponentBase
|
||||
private ISnackbar Snackbar { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private DatabaseClient DatabaseClient { get; init; } = null!;
|
||||
private EmbeddingStore EmbeddingStore { get; init; } = null!;
|
||||
|
||||
private static readonly Assembly ASSEMBLY = Assembly.GetExecutingAssembly();
|
||||
private static readonly MetaDataAttribute META_DATA = ASSEMBLY.GetCustomAttribute<MetaDataAttribute>()!;
|
||||
@ -59,9 +59,9 @@ public partial class Information : MSGComponentBase
|
||||
|
||||
private string VersionPdfium => $"{T("Used PDFium version")}: v{META_DATA_LIBRARIES.PdfiumVersion}";
|
||||
|
||||
private string VersionDatabase => this.DatabaseClient.IsAvailable
|
||||
? $"{T("Database version")}: {this.DatabaseClient.Name} v{META_DATA_DATABASES.DatabaseVersion}"
|
||||
: $"{T("Database")}: {this.DatabaseClient.Name} - {T("not available")}";
|
||||
private string VersionDatabase => this.EmbeddingStore.IsAvailable
|
||||
? $"{T("Database version")}: {this.EmbeddingStore.Name} v{META_DATA_DATABASES.DatabaseVersion}"
|
||||
: $"{T("Database")}: {this.EmbeddingStore.Name} - {T("not available")}";
|
||||
|
||||
private string versionPandoc = TB("Determine Pandoc version, please wait...");
|
||||
private PandocInstallation pandocInstallation;
|
||||
@ -130,7 +130,7 @@ public partial class Information : MSGComponentBase
|
||||
this.osLanguage = await this.RustService.ReadUserLanguage();
|
||||
this.logPaths = await this.RustService.GetLogPaths();
|
||||
|
||||
await foreach (var (label, value) in this.DatabaseClient.GetDisplayInfo())
|
||||
await foreach (var (label, value) in this.EmbeddingStore.GetDisplayInfo())
|
||||
{
|
||||
this.databaseDisplayInfo.Add(new DatabaseDisplayInfo(label, value));
|
||||
}
|
||||
|
||||
@ -2,7 +2,6 @@ using AIStudio.Agents;
|
||||
using AIStudio.Agents.AssistantAudit;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools.Databases;
|
||||
using AIStudio.Tools.Databases.Qdrant;
|
||||
using AIStudio.Tools.PluginSystem;
|
||||
using AIStudio.Tools.PluginSystem.Assistants;
|
||||
using AIStudio.Tools.Services;
|
||||
@ -28,7 +27,7 @@ internal sealed class Program
|
||||
public static string API_TOKEN = null!;
|
||||
public static IServiceProvider SERVICE_PROVIDER = null!;
|
||||
public static ILoggerFactory LOGGER_FACTORY = null!;
|
||||
public static DatabaseClient DATABASE_CLIENT = null!;
|
||||
public static EmbeddingStore EMBEDDING_STORE = null!;
|
||||
|
||||
public static async Task Main()
|
||||
{
|
||||
@ -87,47 +86,9 @@ internal sealed class Program
|
||||
return;
|
||||
}
|
||||
|
||||
var qdrantInfo = await rust.GetQdrantInfo();
|
||||
DatabaseClient databaseClient;
|
||||
if (!qdrantInfo.IsAvailable)
|
||||
{
|
||||
Console.WriteLine($"Warning: Qdrant is not available. Starting without vector database. Reason: '{qdrantInfo.UnavailableReason ?? "unknown"}'.");
|
||||
databaseClient = new NoDatabaseClient("Qdrant", qdrantInfo.UnavailableReason);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (qdrantInfo.Path == string.Empty)
|
||||
{
|
||||
Console.WriteLine("Error: Failed to get the Qdrant path from Rust.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (qdrantInfo.PortHttp == 0)
|
||||
{
|
||||
Console.WriteLine("Error: Failed to get the Qdrant HTTP port from Rust.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (qdrantInfo.PortGrpc == 0)
|
||||
{
|
||||
Console.WriteLine("Error: Failed to get the Qdrant gRPC port from Rust.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (qdrantInfo.Fingerprint == string.Empty)
|
||||
{
|
||||
Console.WriteLine("Error: Failed to get the Qdrant fingerprint from Rust.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (qdrantInfo.ApiToken == string.Empty)
|
||||
{
|
||||
Console.WriteLine("Error: Failed to get the Qdrant API token from Rust.");
|
||||
return;
|
||||
}
|
||||
|
||||
databaseClient = new QdrantClientImplementation("Qdrant", qdrantInfo.Path, qdrantInfo.PortHttp, qdrantInfo.PortGrpc, qdrantInfo.Fingerprint, qdrantInfo.ApiToken);
|
||||
}
|
||||
var embeddingStoreConfig = await rust.GetEmbeddingStoreConfiguration(EmbeddingStoreKind.QDRANT_REMOTE);
|
||||
|
||||
EmbeddingStore embeddingStore = EmbeddingStoreFactory.Create(embeddingStoreConfig);
|
||||
|
||||
var builder = WebApplication.CreateBuilder();
|
||||
builder.WebHost.ConfigureKestrel(kestrelServerOptions =>
|
||||
@ -173,6 +134,7 @@ internal sealed class Program
|
||||
builder.Services.AddSingleton<ThreadSafeRandom>();
|
||||
builder.Services.AddSingleton<VoiceRecordingAvailabilityService>();
|
||||
builder.Services.AddSingleton<DataSourceService>();
|
||||
builder.Services.AddSingleton<DataSourceEmbeddingService>();
|
||||
builder.Services.AddScoped<PandocAvailabilityService>();
|
||||
builder.Services.AddTransient<HTMLParser>();
|
||||
builder.Services.AddTransient<AgentDataSourceSelection>();
|
||||
@ -183,7 +145,8 @@ internal sealed class Program
|
||||
builder.Services.AddHostedService<UpdateService>();
|
||||
builder.Services.AddHostedService<TemporaryChatService>();
|
||||
builder.Services.AddHostedService<EnterpriseEnvironmentService>();
|
||||
builder.Services.AddSingleton(databaseClient);
|
||||
builder.Services.AddHostedService(sp => sp.GetRequiredService<DataSourceEmbeddingService>());
|
||||
builder.Services.AddSingleton(embeddingStore);
|
||||
builder.Services.AddHostedService<GlobalShortcutService>();
|
||||
builder.Services.AddHostedService<RustAvailabilityMonitorService>();
|
||||
|
||||
@ -243,9 +206,9 @@ internal sealed class Program
|
||||
RUST_SERVICE = rust;
|
||||
ENCRYPTION = encryption;
|
||||
|
||||
var databaseLogger = app.Services.GetRequiredService<ILogger<DatabaseClient>>();
|
||||
databaseClient.SetLogger(databaseLogger);
|
||||
DATABASE_CLIENT = databaseClient;
|
||||
var databaseLogger = app.Services.GetRequiredService<ILogger<EmbeddingStore>>();
|
||||
embeddingStore.SetLogger(databaseLogger);
|
||||
EMBEDDING_STORE = embeddingStore;
|
||||
|
||||
programLogger.LogInformation("Initialize internal file system.");
|
||||
app.Use(Redirect.HandlerContentAsync);
|
||||
@ -283,7 +246,7 @@ internal sealed class Program
|
||||
await serverTask;
|
||||
|
||||
RUST_SERVICE.Dispose();
|
||||
DATABASE_CLIENT.Dispose();
|
||||
EMBEDDING_STORE.Dispose();
|
||||
PluginFactory.Dispose();
|
||||
programLogger.LogInformation("The AI Studio server was stopped.");
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ public sealed partial class Routes
|
||||
{
|
||||
public const string HOME = "/";
|
||||
public const string CHAT = "/chat";
|
||||
public const string EMBEDDINGS = "/embeddings";
|
||||
public const string ABOUT = "/about";
|
||||
public const string ASSISTANTS = "/assistants";
|
||||
public const string SETTINGS = "/settings";
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
namespace AIStudio.Tools.Databases;
|
||||
|
||||
public sealed record EmbeddingStoragePoint(
|
||||
string PointId,
|
||||
IReadOnlyList<float> Vector,
|
||||
string DataSourceId,
|
||||
string DataSourceName,
|
||||
string DataSourceType,
|
||||
string FilePath,
|
||||
string FileName,
|
||||
string RelativePath,
|
||||
int ChunkIndex,
|
||||
string Text,
|
||||
string Fingerprint,
|
||||
DateTime LastWriteUtc,
|
||||
DateTime EmbeddedAtUtc);
|
||||
@ -1,6 +1,6 @@
|
||||
namespace AIStudio.Tools.Databases;
|
||||
|
||||
public abstract class DatabaseClient(string name, string path)
|
||||
public abstract class EmbeddingStore(string name, string path)
|
||||
{
|
||||
public string Name => name;
|
||||
|
||||
@ -8,7 +8,7 @@ public abstract class DatabaseClient(string name, string path)
|
||||
|
||||
private string Path => path;
|
||||
|
||||
private ILogger<DatabaseClient>? logger;
|
||||
private ILogger<EmbeddingStore>? logger;
|
||||
|
||||
public abstract IAsyncEnumerable<(string Label, string Value)> GetDisplayInfo();
|
||||
|
||||
@ -45,10 +45,19 @@ public abstract class DatabaseClient(string name, string path)
|
||||
return $"{size:0##} {suffixes[suffixIndex]}";
|
||||
}
|
||||
|
||||
public void SetLogger(ILogger<DatabaseClient> logService)
|
||||
public void SetLogger(ILogger<EmbeddingStore> logService)
|
||||
{
|
||||
this.logger = logService;
|
||||
}
|
||||
|
||||
|
||||
public abstract Task EnsureEmbeddingStoreExists(string collectionName, int vectorSize, CancellationToken token);
|
||||
|
||||
public abstract Task InsertEmbedding(string collectionName, IReadOnlyList<EmbeddingStoragePoint> points, CancellationToken token);
|
||||
|
||||
public abstract Task DeleteEmbeddingByFile(string collectionName, string filePath, CancellationToken token);
|
||||
|
||||
public abstract Task DeleteEmbeddingStore(string collectionName, CancellationToken token);
|
||||
|
||||
public abstract void Dispose();
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
using AIStudio.Tools.Databases.Qdrant;
|
||||
|
||||
namespace AIStudio.Tools.Databases;
|
||||
|
||||
public class EmbeddingStoreFactory
|
||||
{
|
||||
public static EmbeddingStore Create(EmbeddingStoreConfiguration configuration) => configuration.Kind switch
|
||||
{
|
||||
EmbeddingStoreKind.NONE => new NoEmbeddingStore(configuration.Name, configuration.UnavailableReason ?? "unknown"),
|
||||
_ when configuration.Location is null => new NoEmbeddingStore(configuration.Name, $"No location specified for {configuration.Name}"),
|
||||
EmbeddingStoreKind.QDRANT_REMOTE when configuration.Location is RemoteLocation location=> new QdrantClientImplementation(configuration.Name, location.Path, location.HttpPort, location.GrpcPort, location.Fingerprint, location.ApiToken),
|
||||
_ => throw new ArgumentException("Invalid configuration for " + configuration.Name, nameof(configuration)),
|
||||
};
|
||||
}
|
||||
|
||||
public enum EmbeddingStoreKind
|
||||
{
|
||||
NONE,
|
||||
QDRANT_EMBED,
|
||||
QDRANT_REMOTE,
|
||||
}
|
||||
|
||||
public abstract record EmbeddingStoreLocation;
|
||||
|
||||
public sealed record EmbeddedLocation(string Path) : EmbeddingStoreLocation;
|
||||
|
||||
public sealed record RemoteLocation(string Path, int? HttpPort, int? GrpcPort, string? Fingerprint, string? ApiToken) : EmbeddingStoreLocation;
|
||||
|
||||
public sealed record EmbeddingStoreConfiguration(
|
||||
EmbeddingStoreKind Kind,
|
||||
string Name,
|
||||
EmbeddingStoreLocation? Location,
|
||||
string? UnavailableReason);
|
||||
@ -1,24 +0,0 @@
|
||||
using AIStudio.Tools.PluginSystem;
|
||||
|
||||
namespace AIStudio.Tools.Databases;
|
||||
|
||||
public sealed class NoDatabaseClient(string name, string? unavailableReason) : DatabaseClient(name, string.Empty)
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(NoDatabaseClient).Namespace, nameof(NoDatabaseClient));
|
||||
|
||||
public override bool IsAvailable => false;
|
||||
|
||||
public override async IAsyncEnumerable<(string Label, string Value)> GetDisplayInfo()
|
||||
{
|
||||
yield return (TB("Status"), TB("Unavailable"));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(unavailableReason))
|
||||
yield return (TB("Reason"), unavailableReason);
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
39
app/MindWork AI Studio/Tools/Databases/NoEmbeddingStore.cs
Normal file
39
app/MindWork AI Studio/Tools/Databases/NoEmbeddingStore.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using AIStudio.Tools.PluginSystem;
|
||||
|
||||
namespace AIStudio.Tools.Databases;
|
||||
|
||||
public sealed class NoEmbeddingStore(string name, string? unavailableReason) : EmbeddingStore(name, string.Empty)
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(NoEmbeddingStore).Namespace, nameof(NoEmbeddingStore));
|
||||
|
||||
public override bool IsAvailable => false;
|
||||
|
||||
public override async IAsyncEnumerable<(string Label, string Value)> GetDisplayInfo()
|
||||
{
|
||||
yield return (TB("Status"), TB("Unavailable"));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(unavailableReason))
|
||||
yield return (TB("Reason"), unavailableReason);
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override Task EnsureEmbeddingStoreExists(string collectionName, int vectorSize, CancellationToken token) => throw this.BuildUnavailableException();
|
||||
|
||||
public override Task InsertEmbedding(string collectionName, IReadOnlyList<EmbeddingStoragePoint> points, CancellationToken token) => throw this.BuildUnavailableException();
|
||||
|
||||
public override Task DeleteEmbeddingByFile(string collectionName, string filePath, CancellationToken token) => Task.CompletedTask;
|
||||
|
||||
public override Task DeleteEmbeddingStore(string collectionName, CancellationToken token) => Task.CompletedTask;
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
private InvalidOperationException BuildUnavailableException()
|
||||
{
|
||||
return new InvalidOperationException(string.IsNullOrWhiteSpace(unavailableReason)
|
||||
? "The vector database is not available."
|
||||
: unavailableReason);
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,11 @@
|
||||
using Qdrant.Client;
|
||||
using Qdrant.Client.Grpc;
|
||||
using AIStudio.Tools.PluginSystem;
|
||||
using static Qdrant.Client.Grpc.Conditions;
|
||||
|
||||
namespace AIStudio.Tools.Databases.Qdrant;
|
||||
|
||||
public class QdrantClientImplementation : DatabaseClient
|
||||
public class QdrantClientImplementation : EmbeddingStore
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(QdrantClientImplementation).Namespace, nameof(QdrantClientImplementation));
|
||||
|
||||
@ -18,12 +19,12 @@ public class QdrantClientImplementation : DatabaseClient
|
||||
|
||||
private string ApiToken { get; }
|
||||
|
||||
public QdrantClientImplementation(string name, string path, int httpPort, int grpcPort, string fingerprint, string apiToken): base(name, path)
|
||||
public QdrantClientImplementation(string name, string path, int? httpPort, int? grpcPort, string? fingerprint, string? apiToken): base(name, path)
|
||||
{
|
||||
this.HttpPort = httpPort;
|
||||
this.GrpcPort = grpcPort;
|
||||
this.Fingerprint = fingerprint;
|
||||
this.ApiToken = apiToken;
|
||||
this.HttpPort = httpPort ?? 0;
|
||||
this.GrpcPort = grpcPort ?? 0;
|
||||
this.Fingerprint = fingerprint ?? string.Empty;
|
||||
this.ApiToken = apiToken ?? string.Empty;
|
||||
this.GrpcClient = this.CreateQdrantClient();
|
||||
}
|
||||
|
||||
@ -62,5 +63,56 @@ public class QdrantClientImplementation : DatabaseClient
|
||||
yield return (TB("Number of collections"), await this.GetCollectionsAmount());
|
||||
}
|
||||
|
||||
public override async Task EnsureEmbeddingStoreExists(string collectionName, int vectorSize, CancellationToken token)
|
||||
{
|
||||
var exists = await this.GrpcClient.CollectionExistsAsync(collectionName, token);
|
||||
if (exists)
|
||||
return;
|
||||
|
||||
await this.GrpcClient.CreateCollectionAsync(
|
||||
collectionName,
|
||||
new VectorParams
|
||||
{
|
||||
Size = (ulong)vectorSize,
|
||||
Distance = Distance.Cosine,
|
||||
},
|
||||
cancellationToken: token);
|
||||
}
|
||||
|
||||
public override Task InsertEmbedding(string collectionName, IReadOnlyList<EmbeddingStoragePoint> points, CancellationToken token)
|
||||
{
|
||||
var qdrantPoints = points.Select(point => new PointStruct
|
||||
{
|
||||
Id = Guid.Parse(point.PointId),
|
||||
Vectors = point.Vector.ToArray(),
|
||||
Payload =
|
||||
{
|
||||
["data_source_id"] = point.DataSourceId,
|
||||
["data_source_name"] = point.DataSourceName,
|
||||
["data_source_type"] = point.DataSourceType,
|
||||
["file_path"] = point.FilePath,
|
||||
["file_name"] = point.FileName,
|
||||
["relative_path"] = point.RelativePath,
|
||||
["chunk_index"] = (long)point.ChunkIndex,
|
||||
["text"] = point.Text,
|
||||
["fingerprint"] = point.Fingerprint,
|
||||
["last_write_utc"] = point.LastWriteUtc.ToString("O"),
|
||||
["embedded_at_utc"] = point.EmbeddedAtUtc.ToString("O"),
|
||||
}
|
||||
}).ToList();
|
||||
|
||||
return this.GrpcClient.UpsertAsync(collectionName, qdrantPoints, true, null, null, token);
|
||||
}
|
||||
|
||||
public override Task DeleteEmbeddingByFile(string collectionName, string filePath, CancellationToken token)
|
||||
{
|
||||
return this.GrpcClient.DeleteAsync(collectionName, MatchKeyword("file_path", filePath), true, null, null, token);
|
||||
}
|
||||
|
||||
public override Task DeleteEmbeddingStore(string collectionName, CancellationToken token)
|
||||
{
|
||||
return this.GrpcClient.DeleteCollectionAsync(collectionName, cancellationToken: token);
|
||||
}
|
||||
|
||||
public override void Dispose() => this.GrpcClient.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,6 +37,7 @@ public enum Event
|
||||
|
||||
// RAG events:
|
||||
RAG_AUTO_DATA_SOURCES_SELECTED,
|
||||
RAG_EMBEDDING_STATUS_CHANGED,
|
||||
|
||||
// File attachment events:
|
||||
REGISTER_FILE_DROP_AREA,
|
||||
|
||||
1055
app/MindWork AI Studio/Tools/Services/DataSourceEmbeddingService.cs
Normal file
1055
app/MindWork AI Studio/Tools/Services/DataSourceEmbeddingService.cs
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,9 +1,46 @@
|
||||
using AIStudio.Tools.Rust;
|
||||
using AIStudio.Tools.Databases;
|
||||
using AIStudio.Tools.Rust;
|
||||
|
||||
namespace AIStudio.Tools.Services;
|
||||
|
||||
public sealed partial class RustService
|
||||
{
|
||||
public async Task<EmbeddingStoreConfiguration> GetEmbeddingStoreConfiguration(EmbeddingStoreKind kind)
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case EmbeddingStoreKind.QDRANT_REMOTE:
|
||||
{
|
||||
var qdrantInfo = await this.GetQdrantInfo();
|
||||
var invalidFields = new List<string>();
|
||||
if (!qdrantInfo.IsAvailable)
|
||||
invalidFields.Add(qdrantInfo.UnavailableReason ?? "unknown");
|
||||
if (string.IsNullOrWhiteSpace(qdrantInfo.Path))
|
||||
invalidFields.Add("Path");
|
||||
if (qdrantInfo.PortHttp == 0)
|
||||
invalidFields.Add("HttpPort");
|
||||
if (qdrantInfo.PortGrpc == 0)
|
||||
invalidFields.Add("GrpcPort");
|
||||
if (string.IsNullOrWhiteSpace(qdrantInfo.Fingerprint))
|
||||
invalidFields.Add("Fingerprint");
|
||||
if (string.IsNullOrWhiteSpace(qdrantInfo.ApiToken))
|
||||
invalidFields.Add("ApiToken");
|
||||
if (invalidFields.Count <= 0) return new EmbeddingStoreConfiguration(kind, "Qdrant", new RemoteLocation(qdrantInfo.Path, qdrantInfo.PortHttp, qdrantInfo.PortGrpc, qdrantInfo.Fingerprint, qdrantInfo.ApiToken), null);
|
||||
var reason = string.Join(", ", invalidFields);
|
||||
Console.WriteLine($"Warning: Qdrant is not available. Starting without vector database. Reason: '{reason}'.");
|
||||
|
||||
return new EmbeddingStoreConfiguration(
|
||||
EmbeddingStoreKind.NONE,
|
||||
"Qdrant",
|
||||
null,
|
||||
reason);
|
||||
|
||||
}
|
||||
default:
|
||||
return new EmbeddingStoreConfiguration(kind, kind.ToString(), null, $"No configuration available for {kind}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<QdrantInfo> GetQdrantInfo()
|
||||
{
|
||||
try
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace AIStudio.Tools.Services;
|
||||
|
||||
@ -48,6 +49,9 @@ public sealed partial class RustService
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
if (this.TryLogSseErrorMessage(jsonContent, path))
|
||||
continue;
|
||||
|
||||
this.logger?.LogError("Failed to deserialize SSE event: {JsonContent}", jsonContent);
|
||||
}
|
||||
}
|
||||
@ -65,4 +69,77 @@ public sealed partial class RustService
|
||||
|
||||
return resultBuilder.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<string> StreamArbitraryFileData(string path, bool extractImages = false, [EnumeratorCancellation] CancellationToken token = default)
|
||||
{
|
||||
var streamId = Guid.NewGuid().ToString();
|
||||
var requestUri = $"/retrieval/fs/extract?path={Uri.EscapeDataString(path)}&stream_id={streamId}&extract_images={extractImages}";
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
|
||||
using var response = await this.http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
yield break;
|
||||
|
||||
string? finalContentChunk = null;
|
||||
try
|
||||
{
|
||||
await using var stream = await response.Content.ReadAsStreamAsync(token);
|
||||
using var reader = new StreamReader(stream);
|
||||
|
||||
while (!reader.EndOfStream && !token.IsCancellationRequested)
|
||||
{
|
||||
var line = await reader.ReadLineAsync(token);
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
continue;
|
||||
|
||||
if (!line.StartsWith("data:", StringComparison.InvariantCulture))
|
||||
continue;
|
||||
|
||||
var jsonContent = line[5..];
|
||||
ContentStreamSseEvent? sseEvent = null;
|
||||
try
|
||||
{
|
||||
sseEvent = JsonSerializer.Deserialize<ContentStreamSseEvent>(jsonContent);
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
if (this.TryLogSseErrorMessage(jsonContent, path))
|
||||
continue;
|
||||
|
||||
this.logger?.LogError("Failed to deserialize SSE event: {JsonContent}", jsonContent);
|
||||
}
|
||||
|
||||
if (sseEvent is null)
|
||||
continue;
|
||||
|
||||
var content = ContentStreamSseHandler.ProcessEvent(sseEvent, extractImages);
|
||||
if (!string.IsNullOrWhiteSpace(content))
|
||||
yield return content;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
finalContentChunk = ContentStreamSseHandler.Clear(streamId);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(finalContentChunk))
|
||||
yield return finalContentChunk;
|
||||
}
|
||||
|
||||
private bool TryLogSseErrorMessage(string jsonContent, string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
var errorMessage = JsonSerializer.Deserialize<string>(jsonContent);
|
||||
if (string.IsNullOrWhiteSpace(errorMessage))
|
||||
return false;
|
||||
|
||||
this.logger?.LogError("Rust retrieval stream error for '{Path}': {ErrorMessage}", path, errorMessage);
|
||||
return true;
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user