@attribute [Route(Routes.EMBEDDINGS)] @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.") @string.Format(T("Indexed files: {0}"), this.TotalIndexedFiles) @string.Format(T("Pending files: {0}"), this.TotalPendingFiles) @string.Format(T("Skipped files: {0}"), this.TotalPermanentlySkippedFiles) @string.Format(T("Failed files: {0}"), this.TotalFailedFiles) @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. *@ }
@string.Format(T("{0} of {1} files are indexed."), status.IndexedFiles, status.TotalFiles) @if (status.PermanentlySkippedFiles > 0) { @string.Format(T("Skipped files: {0}. AI Studio reads them again once they change."), status.PermanentlySkippedFiles) } @if (status.FailedFiles > 0) { @string.Format(T("Failed files: {0}"), status.FailedFiles) } @if (!string.IsNullOrWhiteSpace(status.CurrentFile)) { @string.Format(T("Current file: {0}"), status.CurrentFile) } @* 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) { @foreach (var group in this.GetFailureGroups(status)) { @(group.Cause.IsPermanent ? 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") } @T("File") @T("Noticed") @T("Actions") @* Captions render as spans, so each of them needs to be told to take its own line. *@ @GetFileName(context.FilePath) @context.FilePath @if (group.Cause.ShowsMessagePerFile) { @context.Reason } @GetOccurrenceText(context) @if (CanShowInFileManager(context)) { } } } @* 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 }
}
}