@attribute [Route(Routes.EMBEDDINGS)] @using AIStudio.Tools.Services @inherits MSGComponentBase @T("Background embeddings") @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. The same applies to documents without readable text, such as scanned pages: AI Studio remembers them and reads them again only once they change.") @T("AI Studio indexes large data sources in steps of a few minutes, so that your other data sources get their turn in between.") @this.PauseBetweenStepsText @if (this.AreMailboxesEnabled) { @T("Mailboxes are synced every 16 minutes while the automatic refresh is on. Each mail is embedded only once: when it moves to another folder, AI Studio merely notes where it lies now.") } @string.Format(T("Indexed: {0}"), this.FormatNumber(this.TotalIndexedDocuments)) @string.Format(T("Pending: {0}"), this.FormatNumber(this.TotalPendingDocuments)) @string.Format(T("Skipped: {0}"), this.FormatNumber(this.TotalPermanentlySkippedDocuments)) @string.Format(T("Failed: {0}"), this.FormatNumber(this.TotalFailedDocuments)) @if (this.IsWorkingThroughDataSources) { @string.Format(T("Data source {0} of {1} is being worked on. The others are waiting their turn."), this.CurrentDataSourceNumber, this.Statuses.Count) } @if (this.Statuses.Count == 0) { @T("No local data source has been queued for embedding yet.") } else { @* One panel per data source, and only one of them open: a folder with thousands of files fills the page by itself. What a closed panel still has to say stays in its header, so nobody has to open every one of them to see how their data sources are doing. *@ @foreach (var status in this.Statuses) {
@string.Format(T("Data source: {0}"), status.DataSourceName) @status.StateLabel @if (CanRefresh(status)) { @* This opens or closes the panel along the way. The header is what toggles it, and neither stopping the event nor swallowing the click works in this project — see the end button of our own ExpansionPanel component, which behaves the same way. *@ } @if (this.CanRepair(status)) { }
@this.GetProgressText(status) @if (IsMailbox(status)) { @this.GetLastSyncText(status) } @if (status.SkippedDocuments > 0) { @this.GetSkippedText(status) } @if (status.FailedDocuments > 0) { @this.GetFailedText(status) } @if (!string.IsNullOrWhiteSpace(status.CurrentDocument)) { @this.GetCurrentDocumentText(status) } @* One panel per cause, the most pressing one first. A folder in which nine hundred scanned documents were skipped otherwise buries the handful of files somebody has to look at. *@ @if (status.Failures.Count > 0 || status.IgnoredFiles.Count > 0) { @foreach (var group in this.GetFailureGroups(status)) { @(group.Cause.IsPermanent ? (IsMailbox(status) ? T("Skipped") : T("Skipped until the file changes")) : T("Tried again during the next run")) @if (!string.IsNullOrWhiteSpace(group.EmbeddingProviderName)) { @string.Format(T("Embedding provider: {0}"), group.EmbeddingProviderName) } @if (group.Cause.NeedsProviderSettings) { @T("Open the settings") } @(IsMailbox(status) ? T("Mail") : T("File")) @T("Noticed") @T("Actions") @* Captions render as spans, so each of them needs to be told to take its own line. *@ @if (IsMailbox(status)) { @GetMailName(context) } else { @GetFileName(context.DocumentKey) @context.DocumentKey } @if (group.Cause.ShowsMessagePerFile) { @context.Reason } @GetOccurrenceText(context) @if (CanShowInFileManager(status, context)) { } } @foreach (var group in this.GetIgnoredFileGroups(status)) { @group.Cause.Description @T("Not added to the index") @if (group.Cause.NeedsDataSourceSettings || group.Cause.NeedsTranscriptionSettings) { } @if (group.Cause.NeedsTranscriptionSettings) { @T("Open the transcription settings") } @if (group.Cause.NeedsDataSourceSettings) { @T("Manage the data sources") } @T("File") @T("Actions") @GetFileName(context.FilePath) @context.FilePath @if (CanShowInFileManager(status, context.FilePath)) { } } } @if (status.FilePatternExclusions.Count > 0 || status.ExcludedFolders.Count > 0) { @T("Outside the selection") @T("Manage the data sources") @T("These file types and folders were found in the folder of the data source, but its file patterns or its list of excluded files and folders leave them out. They are intentionally excluded and do not affect the indexing status. Excluded folders are not searched at all.") @foreach (var group in status.ExcludedFolders) { @this.GetExcludedFolderGroupText(group) } @foreach (var group in status.FilePatternExclusions) { @this.GetFilePatternExclusionGroupText(group) } } @* Shown next to the list, not instead of it: the list says which files failed, while this is the one sentence about the data source as a whole. Hiding it as soon as a single file failed is what made it invisible in practice. *@ @if (!string.IsNullOrWhiteSpace(status.LastError)) { @status.LastError } @* What the user has to decide comes with the buttons to decide it. The sentence above says what is at stake; reading it and then having to find the settings of the mailbox or the data source is where people give up. *@ @if (status.Attention is DataSourceAttention.AUTH_FAILED) { @T("Change the password") @T("Try to sign in again") } else if (status is { Attention: DataSourceAttention.MASS_REMOVAL_PENDING, PendingRemovalCount: not null }) { @T("Remove them from the index") @T("Change the settings of the mailbox") } else if (status.Attention is DataSourceAttention.LOCATION_UNREACHABLE && this.CanEditDataSource(status)) { @T("Edit this data source") }
}
}