M25: expose provenance-aware knowledge statistics

This commit is contained in:
NuklearRabbit
2026-08-10 16:04:39 +02:00
parent 0935901f11
commit 90cc3cf378
22 changed files with 238 additions and 10 deletions
+5
View File
@@ -333,6 +333,11 @@ export interface KnowledgeHealth {
workspace: string;
collection: string;
document_count: number | null;
source_document_count: number;
reported_synced_document_count: number | null;
reported_failed_document_count: number | null;
last_sync_at: string | null;
statistics_state: "verified" | "sync_reported" | "not_reported";
}
export interface N8nWorkflowEvidence {
@@ -69,7 +69,7 @@
"n8nNoEvidence": "No workflow evidence recorded",
"knowledgeTitle": "Knowledge assistant",
"knowledgeSummary": "{{count}} procedures indexed",
"knowledgeIndexUnknown": "Knowledge source available · index size unknown",
"knowledgeIndexUnknown": "{{count}} knowledge sources · index size not verifiable",
"knowledgeUnavailable": "Health check unavailable",
"mcpTitle": "MCP Hub",
"mcpEnabled": "Registration enabled",
@@ -12,7 +12,8 @@
"knowledgeTitle": "Knowledge assistant",
"knowledgeSummaryDemo": "Demo knowledge base · {{count}} procedures indexed in {{collection}}.",
"knowledgeSummaryRagcore": "RAGcore · {{count}} procedures indexed in {{collection}}.",
"knowledgeSummaryIndexUnknown": "Knowledge source available · index size is unavailable for {{collection}}.",
"knowledgeSummaryIndexUnknown": "RAGcore reachable · {{sourceCount}} source documents for this language · index size not verifiable for {{collection}}.",
"knowledgeSummarySyncReported": "RAGcore reachable · {{sourceCount}} sources for this language · latest sync reported {{synced}} processed, {{failed}} failed at {{when}} · index size not verifiable for {{collection}}.",
"knowledgeUnavailable": "Health evidence is currently unavailable.",
"gatewayKicker": "Tool gateway",
"mcpTitle": "MCP Hub",
@@ -12,6 +12,13 @@
"statusUnavailable": "Unavailable",
"proceduresIndexed": "{{count}} procedures indexed",
"proceduresIndexUnknown": "Index size is not available through this connection",
"statistics": {
"sourceDocuments": "source documents in this language",
"reportedSynced": "sync reported by n8n",
"reportedFailed": "sync failures reported",
"lastSync": "Latest sync report: {{when}}. RAGcore does not expose a verifiable index size, so sync counts are not presented as indexed documents.",
"noSyncReport": "No sync report has been received yet. RAGcore does not expose a verifiable index size."
},
"providerNote": "This demo answers from a small, fixed set of indexed procedures — not a live RAGcore connection. A live RAGcore backend will later take over the same interface without changing how this page works.",
"askHeading": "Ask a procedure question",
"askSubheading": "Retrieval → evidence check → grounded answer",
@@ -69,7 +69,7 @@
"n8nNoEvidence": "Aucune preuve d'automatisation enregistrée",
"knowledgeTitle": "Assistant de connaissances",
"knowledgeSummary": "{{count}} procédures indexées",
"knowledgeIndexUnknown": "Source de connaissances disponible · taille dindex inconnue",
"knowledgeIndexUnknown": "{{count}} sources de connaissances · taille dindex non vérifiable",
"knowledgeUnavailable": "Vérification de santé indisponible",
"mcpTitle": "MCP Hub",
"mcpEnabled": "Enregistrement activé",
@@ -12,7 +12,8 @@
"knowledgeTitle": "Assistant de connaissances",
"knowledgeSummaryDemo": "Base de connaissances de démo · {{count}} procédures indexées dans {{collection}}.",
"knowledgeSummaryRagcore": "RAGcore · {{count}} procédures indexées dans {{collection}}.",
"knowledgeSummaryIndexUnknown": "Source de connaissances disponible · taille dindex indisponible pour {{collection}}.",
"knowledgeSummaryIndexUnknown": "RAGcore accessible · {{sourceCount}} documents sources dans cette langue · taille dindex non vérifiable pour {{collection}}.",
"knowledgeSummarySyncReported": "RAGcore accessible · {{sourceCount}} sources dans cette langue · dernier rapport : {{synced}} traités, {{failed}} échoués à {{when}} · taille dindex non vérifiable pour {{collection}}.",
"knowledgeUnavailable": "Preuves de santé actuellement indisponibles.",
"gatewayKicker": "Passerelle d'outils",
"mcpTitle": "MCP Hub",
@@ -12,6 +12,13 @@
"statusUnavailable": "Indisponible",
"proceduresIndexed": "{{count}} procédures indexées",
"proceduresIndexUnknown": "La taille de lindex nest pas disponible via cette connexion",
"statistics": {
"sourceDocuments": "documents sources dans cette langue",
"reportedSynced": "synchronisation signalée par n8n",
"reportedFailed": "échecs de synchronisation signalés",
"lastSync": "Dernier rapport de synchronisation : {{when}}. RAGcore nexpose pas de taille dindex vérifiable ; les nombres synchronisés ne sont donc pas présentés comme des documents indexés.",
"noSyncReport": "Aucun rapport de synchronisation reçu. RAGcore nexpose pas de taille dindex vérifiable."
},
"providerNote": "Cette démo répond à partir d'un petit ensemble fixe de procédures indexées — pas d'une connexion RAGcore en direct. Un backend RAGcore en direct reprendra plus tard la même interface sans changer le fonctionnement de cette page.",
"askHeading": "Poser une question de procédure",
"askSubheading": "Recherche → vérification des preuves → réponse étayée",
@@ -69,7 +69,7 @@
"n8nNoEvidence": "Geen automatiseringsevidentie geregistreerd",
"knowledgeTitle": "Kennisassistent",
"knowledgeSummary": "{{count}} procedures geïndexeerd",
"knowledgeIndexUnknown": "Kennisbron bereikbaar · indexomvang onbekend",
"knowledgeIndexUnknown": "{{count}} kennisbronnen · indexomvang niet verifieerbaar",
"knowledgeUnavailable": "Statuscontrole niet beschikbaar",
"mcpTitle": "MCP Hub",
"mcpEnabled": "Registratie ingeschakeld",
@@ -12,7 +12,8 @@
"knowledgeTitle": "Kennisassistent",
"knowledgeSummaryDemo": "Demokennisbank · {{count}} procedures geïndexeerd in {{collection}}.",
"knowledgeSummaryRagcore": "RAGcore · {{count}} procedures geïndexeerd in {{collection}}.",
"knowledgeSummaryIndexUnknown": "Kennisbron bereikbaar · indexomvang niet beschikbaar voor {{collection}}.",
"knowledgeSummaryIndexUnknown": "RAGcore bereikbaar · {{sourceCount}} brondocumenten voor deze taal · indexomvang niet verifieerbaar voor {{collection}}.",
"knowledgeSummarySyncReported": "RAGcore bereikbaar · {{sourceCount}} bronnen voor deze taal · laatste syncrapport {{synced}} verwerkt, {{failed}} mislukt op {{when}} · indexomvang niet verifieerbaar voor {{collection}}.",
"knowledgeUnavailable": "Statusevidentie momenteel niet beschikbaar.",
"gatewayKicker": "Tool-gateway",
"mcpTitle": "MCP Hub",
@@ -12,6 +12,13 @@
"statusUnavailable": "Niet beschikbaar",
"proceduresIndexed": "{{count}} procedures geïndexeerd",
"proceduresIndexUnknown": "Indexomvang niet beschikbaar via deze koppeling",
"statistics": {
"sourceDocuments": "brondocumenten in deze taal",
"reportedSynced": "sync door n8n gerapporteerd",
"reportedFailed": "syncfouten gerapporteerd",
"lastSync": "Laatste syncrapport: {{when}}. RAGcore stelt geen verifieerbare indexomvang beschikbaar; syncaantallen worden daarom niet als geïndexeerde documenten voorgesteld.",
"noSyncReport": "Nog geen syncrapport ontvangen. RAGcore stelt geen verifieerbare indexomvang beschikbaar."
},
"providerNote": "Deze demo beantwoordt vanuit een kleine, vaste set geïndexeerde procedures — geen live RAGcore-koppeling. Een live RAGcore-backend zal later dezelfde interface overnemen, zonder dat deze pagina verandert.",
"askHeading": "Stel een procedurevraag",
"askSubheading": "Ophalen → evidentiecontrole → onderbouwd antwoord",
+12 -1
View File
@@ -215,7 +215,18 @@ export function Automation() {
? t("cards.checking")
: knowledge
? knowledge.document_count === null
? t("cards.knowledgeSummaryIndexUnknown", { collection: knowledge.collection })
? knowledge.statistics_state === "sync_reported"
? t("cards.knowledgeSummarySyncReported", {
collection: knowledge.collection,
sourceCount: knowledge.source_document_count,
synced: knowledge.reported_synced_document_count,
failed: knowledge.reported_failed_document_count,
when: knowledge.last_sync_at ? formatDateTime(knowledge.last_sync_at) : "—",
})
: t("cards.knowledgeSummaryIndexUnknown", {
collection: knowledge.collection,
sourceCount: knowledge.source_document_count,
})
: t(knowledge.provider === "ragcore" ? "cards.knowledgeSummaryRagcore" : "cards.knowledgeSummaryDemo", {
count: knowledge.document_count,
collection: knowledge.collection,
+1 -1
View File
@@ -245,7 +245,7 @@ export function Dashboard() {
<IntegrationMark kind="rag" />
<div>
<strong>{t("integrationPulse.knowledgeTitle")}</strong>
<span>{knowledge ? (knowledge.document_count === null ? t("integrationPulse.knowledgeIndexUnknown") : t("integrationPulse.knowledgeSummary", { count: knowledge.document_count })) : t("integrationPulse.knowledgeUnavailable")}</span>
<span>{knowledge ? (knowledge.document_count === null ? t("integrationPulse.knowledgeIndexUnknown", { count: knowledge.source_document_count }) : t("integrationPulse.knowledgeSummary", { count: knowledge.document_count })) : t("integrationPulse.knowledgeUnavailable")}</span>
</div>
<StatusBadge
status={knowledge?.available ? "available" : "unavailable"}
+20
View File
@@ -5,6 +5,7 @@ import { describeApiError, type ApiErrorInfo } from "../api/errorMessages";
import type { GroundedAnswer, KnowledgeHealth } from "../api/types";
import { Icon } from "../components/Icons";
import { ApiErrorNotice, PageHeader } from "../components/PageChrome";
import { useLocaleFormat } from "../i18n/format";
import { PRODUCT_NAME } from "../product";
interface Exchange {
@@ -21,6 +22,7 @@ function isOpaqueVersion(value: string): boolean {
export function Knowledge() {
const { t, i18n } = useTranslation(["knowledge", "errors"]);
const { formatDateTime } = useLocaleFormat();
const [status, setStatus] = useState<KnowledgeHealth | null>(null);
const [statusSettled, setStatusSettled] = useState(false);
const [question, setQuestion] = useState("");
@@ -138,6 +140,24 @@ export function Knowledge() {
{statusSettled && status?.provider === "demo" && (
<p className="knowledge-provider-note"><Icon name="shield" /> {t("providerNote")}</p>
)}
{statusSettled && status?.provider === "ragcore" && (
<div className="knowledge-index-evidence" role="status">
<div><strong>{status.source_document_count}</strong><span>{t("statistics.sourceDocuments")}</span></div>
<div>
<strong>{status.reported_synced_document_count ?? "—"}</strong>
<span>{t("statistics.reportedSynced")}</span>
</div>
<div>
<strong>{status.reported_failed_document_count ?? "—"}</strong>
<span>{t("statistics.reportedFailed")}</span>
</div>
<p>
{status.last_sync_at
? t("statistics.lastSync", { when: formatDateTime(status.last_sync_at) })
: t("statistics.noSyncReport")}
</p>
</div>
)}
<form className="panel knowledge-form" onSubmit={handleSubmit} aria-labelledby="ask-heading">
<div className="ask-heading">
+5
View File
@@ -456,6 +456,11 @@ details summary { cursor: pointer; color: var(--teal-dark); }.data-table details
.knowledge-provider-note { display: flex; align-items: flex-start; gap: 8px; margin: -6px 0 18px; padding: 10px 14px; color: var(--ink-soft); background: var(--info-pale); border: 1px solid #cfe3ee; border-radius: var(--radius); font-size: .72rem; line-height: 1.5; }
.knowledge-provider-note svg { width: 15px; flex-shrink: 0; margin-top: 1px; color: var(--info); }
.knowledge-index-evidence { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1px; margin: -6px 0 18px; overflow: hidden; background: var(--line); border: 1px solid var(--line); border-radius: var(--radius); }
.knowledge-index-evidence > div { display: grid; gap: 3px; padding: 11px 14px; background: white; }
.knowledge-index-evidence strong { font-size: .86rem; }
.knowledge-index-evidence span, .knowledge-index-evidence p { color: var(--muted); font-size: .66rem; line-height: 1.45; }
.knowledge-index-evidence p { grid-column: 1 / -1; margin: 0; padding: 9px 14px; background: var(--surface-subtle); }
.knowledge-suggestions { display: flex; flex-wrap: wrap; align-items: center; gap: 7px; margin-top: 12px; }
.knowledge-suggestions > span { color: var(--muted); font-size: .68rem; font-weight: 700; }
.suggestion-chip { padding: 6px 11px; color: var(--teal-dark); background: var(--teal-pale); border: 1px solid #bfe6df; border-radius: 999px; font-size: .68rem; font-weight: 600; cursor: pointer; }