feat(ai-chat): show RAG indexing coverage in AI settings

Display "Indexed N of M pages" on the AI provider settings page so admins
can see how much of the wiki is covered by vector-RAG semantic search.

- page-embedding.repo: add countIndexedPages() — distinct non-deleted pages
  that have stored embeddings in the workspace
- page.repo: add countByWorkspace() — total non-deleted pages
- ai-settings.service: compute both counts in getMasked() (Promise.all) and
  return them with the masked settings; inject PageEmbeddingRepo + PageRepo
- MaskedAiSettings / IAiSettings: add indexedPages + totalPages
- ai-provider-settings: render a dimmed coverage line under "Embedding model"
- i18n: add the "Indexed {{indexed}} of {{total}} pages" key (en-US, ru-RU)
This commit is contained in:
vvzvlad
2026-06-17 23:18:51 +03:00
parent 48c158bb7e
commit 1f2d20244e
8 changed files with 68 additions and 1 deletions

View File

@@ -1,6 +1,8 @@
import { BadRequestException, Injectable } from '@nestjs/common';
import { WorkspaceRepo } from '@docmost/db/repos/workspace/workspace.repo';
import { AiProviderCredentialsRepo } from '@docmost/db/repos/ai-chat/ai-provider-credentials.repo';
import { PageEmbeddingRepo } from '@docmost/db/repos/ai-chat/page-embedding.repo';
import { PageRepo } from '@docmost/db/repos/page/page.repo';
import { SecretBoxService } from '../crypto/secret-box';
import {
AiDriver,
@@ -36,6 +38,8 @@ export class AiSettingsService {
constructor(
private readonly workspaceRepo: WorkspaceRepo,
private readonly aiProviderCredentialsRepo: AiProviderCredentialsRepo,
private readonly pageEmbeddingRepo: PageEmbeddingRepo,
private readonly pageRepo: PageRepo,
private readonly secretBox: SecretBoxService,
) {}
@@ -82,7 +86,8 @@ export class AiSettingsService {
/**
* Masked settings safe for admin clients. NEVER includes the key (even
* encrypted); only `hasApiKey` for the current driver.
* encrypted); only `hasApiKey` for the current driver. Also reports RAG
* indexing coverage (`indexedPages`/`totalPages`) for the settings UI.
*/
async getMasked(workspaceId: string): Promise<MaskedAiSettings> {
const provider = await this.readProvider(workspaceId);
@@ -96,6 +101,11 @@ export class AiSettingsService {
hasApiKey = !!creds?.apiKeyEnc;
}
const [indexedPages, totalPages] = await Promise.all([
this.pageEmbeddingRepo.countIndexedPages(workspaceId),
this.pageRepo.countByWorkspace(workspaceId),
]);
return {
driver: provider.driver,
chatModel: provider.chatModel,
@@ -103,6 +113,8 @@ export class AiSettingsService {
baseUrl: provider.baseUrl,
systemPrompt: provider.systemPrompt,
hasApiKey,
indexedPages,
totalPages,
};
}

View File

@@ -44,4 +44,7 @@ export interface MaskedAiSettings {
baseUrl?: string;
systemPrompt?: string;
hasApiKey: boolean;
// RAG indexing coverage for the settings UI.
indexedPages: number;
totalPages: number;
}