diff --git a/PROJECT_STATE.md b/PROJECT_STATE.md index a15cf9c..f1119f7 100644 --- a/PROJECT_STATE.md +++ b/PROJECT_STATE.md @@ -2574,3 +2574,23 @@ evidence yet." to stable callback/effect dependencies and a lazy route chunk; TypeScript, lint and production build pass. Exact next action: extend RAGcore corpus statistics and health evidence, then run complete acceptance and deploy all production-readiness milestones. + +## M25 — provenance-aware knowledge statistics (2026-08-10) + +- Expanded knowledge health with separate counts for authoritative local source + documents, the latest persisted n8n sync report and documents independently verified + as indexed. The API includes failed sync count, report time and an explicit statistics + provenance state. +- Confirmed against RAGcore's checked-in OpenAPI and route implementation that the + service intentionally exposes identity-based single-document lookup but no corpus-size + or space-browse endpoint. Fleet Ops therefore keeps `document_count=null` for RAGcore + and never mislabels an accepted upload as proven indexing/publication. +- Knowledge, dashboard and Integration Management now present the available source and + sync evidence in all three locales. The deterministic demo provider continues to + report its directly verified per-language corpus count. +- Regenerated the Fleet Ops OpenAPI contract and documented the provenance rules. +- Evidence: focused knowledge/demo suite **36 passed with zero warnings** from a rebuilt + image; ruff/mypy passed; frontend TypeScript lint and production build passed. The + prior Starlette/httpx warning is confirmed absent in the rebuilt environment. +- Exact next action: run complete clean acceptance, push all five milestone commits, + create a verified live backup, redeploy and execute live browser acceptance. diff --git a/backend/app/api/routers/knowledge.py b/backend/app/api/routers/knowledge.py index ffe3f8a..229e31f 100644 --- a/backend/app/api/routers/knowledge.py +++ b/backend/app/api/routers/knowledge.py @@ -100,6 +100,30 @@ def record_feedback( @router.get("/status", response_model=KnowledgeHealth) def knowledge_status( language: SupportedLanguage = "en-GB", + db: Session = Depends(get_db), _user: CurrentUser = Depends(get_current_user), ) -> KnowledgeHealth: - return get_knowledge_provider().health(language) + health = get_knowledge_provider().health(language) + if health.provider != "ragcore": + return health + + latest_sync = db.scalar( + select(AuditEvent) + .where(AuditEvent.action == "n8n_procedures_synced") + .order_by(AuditEvent.occurred_at.desc()) + .limit(1) + ) + if latest_sync is None: + return health + + reported = latest_sync.after_json or {} + synced = reported.get("synced") + failed = reported.get("failed") + return health.model_copy( + update={ + "reported_synced_document_count": synced if isinstance(synced, int) else None, + "reported_failed_document_count": failed if isinstance(failed, int) else None, + "last_sync_at": latest_sync.occurred_at, + "statistics_state": "sync_reported", + } + ) diff --git a/backend/app/services/knowledge/__init__.py b/backend/app/services/knowledge/__init__.py index 2a164a3..2d6e59d 100644 --- a/backend/app/services/knowledge/__init__.py +++ b/backend/app/services/knowledge/__init__.py @@ -1,5 +1,6 @@ from __future__ import annotations +from datetime import datetime from functools import lru_cache from typing import Literal, Protocol @@ -8,6 +9,7 @@ from pydantic import BaseModel from app.core.config import get_settings EvidenceState = Literal["grounded", "insufficient", "unavailable"] +KnowledgeStatisticsState = Literal["verified", "sync_reported", "not_reported"] class SourceCard(BaseModel): @@ -36,6 +38,11 @@ class KnowledgeHealth(BaseModel): # A provider may be healthy without exposing a corpus-size endpoint. `None` means # unknown, never "zero procedures". document_count: int | None + source_document_count: int + reported_synced_document_count: int | None + reported_failed_document_count: int | None + last_sync_at: datetime | None + statistics_state: KnowledgeStatisticsState class KnowledgeProvider(Protocol): diff --git a/backend/app/services/knowledge/demo.py b/backend/app/services/knowledge/demo.py index e922be8..f98d480 100644 --- a/backend/app/services/knowledge/demo.py +++ b/backend/app/services/knowledge/demo.py @@ -322,6 +322,11 @@ class DemoKnowledgeProvider: workspace=self._settings.ragcore_workspace, collection=self._settings.ragcore_collection, document_count=self._document_count_by_language[language], + source_document_count=self._document_count_by_language[language], + reported_synced_document_count=None, + reported_failed_document_count=None, + last_sync_at=None, + statistics_state="verified", ) def _score( diff --git a/backend/app/services/knowledge/ragcore.py b/backend/app/services/knowledge/ragcore.py index a24e478..34b68d1 100644 --- a/backend/app/services/knowledge/ragcore.py +++ b/backend/app/services/knowledge/ragcore.py @@ -1,9 +1,12 @@ from __future__ import annotations +from pathlib import Path + import httpx from app.core.config import get_settings from app.services.knowledge import EvidenceState, GroundedAnswer, KnowledgeHealth, SourceCard +from app.services.knowledge.procedures import iter_procedure_documents _GROUNDED_ANSWERABILITY = {"answerable", "partially_answerable"} @@ -140,6 +143,10 @@ class RAGcoreKnowledgeProvider: except (httpx.HTTPError, ValueError) as exc: available = False detail = f"RAGcore unavailable: {type(exc).__name__}: {exc}" + source_document_count = sum( + document.language == language + for document in iter_procedure_documents(Path(self._settings.knowledge_dir)) + ) return KnowledgeHealth( provider=self.name, available=available, @@ -150,6 +157,11 @@ class RAGcoreKnowledgeProvider: # RAGcore's retrieval API has no corpus-size endpoint. Unknown is explicit # so the UI never turns this into the misleading claim "0 procedures". document_count=None, + source_document_count=source_document_count, + reported_synced_document_count=None, + reported_failed_document_count=None, + last_sync_at=None, + statistics_state="not_reported", ) def ask(self, question: str, correlation_id: str, language: str = "en-GB") -> GroundedAnswer: diff --git a/backend/tests/test_knowledge.py b/backend/tests/test_knowledge.py index 2a987dc..f149646 100644 --- a/backend/tests/test_knowledge.py +++ b/backend/tests/test_knowledge.py @@ -5,7 +5,9 @@ from pathlib import Path import httpx +from app.api.routers import knowledge as knowledge_router from app.core.config import get_settings +from app.services.knowledge import KnowledgeHealth from app.services.knowledge.demo import DemoKnowledgeProvider from app.services.knowledge.ragcore import RAGcoreKnowledgeProvider @@ -73,6 +75,8 @@ def test_demo_provider_health_reports_document_count(): assert health.provider == "demo" assert health.available is True assert health.document_count == 11 + assert health.source_document_count == 11 + assert health.statistics_state == "verified" def test_demo_provider_health_reports_document_count_per_language(): @@ -154,6 +158,46 @@ def test_knowledge_status_endpoint(ops_client): assert response.json()["provider"] == "demo" +def test_ragcore_status_separates_sync_report_from_unverifiable_index( + client, ops_client, monkeypatch +): + class FakeRagcoreProvider: + def health(self, language="en-GB"): + return KnowledgeHealth( + provider="ragcore", + available=True, + detail="ready", + tenant="fleet-ops", + workspace="operations", + collection="internal-procedures", + document_count=None, + source_document_count=11, + reported_synced_document_count=None, + reported_failed_document_count=None, + last_sync_at=None, + statistics_state="not_reported", + ) + + monkeypatch.setattr(knowledge_router, "get_knowledge_provider", FakeRagcoreProvider) + settings = get_settings() + sync = client.post( + "/api/v1/integrations/n8n/procedures-sync-result", + json={"execution_id": "rag-statistics-test", "synced": 32, "failed": 1}, + headers={"X-Service-Token": settings.n8n_callback_token}, + ) + assert sync.status_code == 200 + + response = ops_client.get("/api/v1/knowledge/status?language=nl-BE") + assert response.status_code == 200 + status = response.json() + assert status["document_count"] is None + assert status["source_document_count"] == 11 + assert status["reported_synced_document_count"] == 32 + assert status["reported_failed_document_count"] == 1 + assert status["last_sync_at"] is not None + assert status["statistics_state"] == "sync_reported" + + class _FakeResponse: def __init__(self, status_code: int, body: dict): self.status_code = status_code @@ -224,6 +268,9 @@ def test_ragcore_provider_health_reports_ready_status(monkeypatch): health = provider.health() assert health.provider == "ragcore" assert health.available is True + assert health.document_count is None + assert health.source_document_count == 11 + assert health.statistics_state == "not_reported" def test_ragcore_provider_health_reports_degraded_status(monkeypatch): diff --git a/contracts/openapi.yaml b/contracts/openapi.yaml index 46892c2..184b60b 100644 --- a/contracts/openapi.yaml +++ b/contracts/openapi.yaml @@ -3332,6 +3332,32 @@ components: - type: integer - type: 'null' title: Document Count + source_document_count: + type: integer + title: Source Document Count + reported_synced_document_count: + anyOf: + - type: integer + - type: 'null' + title: Reported Synced Document Count + reported_failed_document_count: + anyOf: + - type: integer + - type: 'null' + title: Reported Failed Document Count + last_sync_at: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Last Sync At + statistics_state: + type: string + enum: + - verified + - sync_reported + - not_reported + title: Statistics State type: object required: - provider @@ -3341,6 +3367,11 @@ components: - workspace - collection - document_count + - source_document_count + - reported_synced_document_count + - reported_failed_document_count + - last_sync_at + - statistics_state title: KnowledgeHealth MaintenanceOut: properties: diff --git a/docs/09-ragcore-integration.md b/docs/09-ragcore-integration.md index 8e9cd40..f222bc3 100644 --- a/docs/09-ragcore-integration.md +++ b/docs/09-ragcore-integration.md @@ -14,7 +14,24 @@ These values are configurable. ## Source documents -The ten Markdown files under `knowledge/procedures/` are authoritative PoC sources. Keep their IDs, versions and effective dates as metadata. +The versioned Markdown files under `knowledge/procedures/` (currently eleven per supported +language) are the authoritative sources. Keep their IDs, versions and effective dates as +metadata. + +## Index statistics and provenance + +`GET /api/v1/knowledge/status` keeps three different measurements separate: + +- `source_document_count`: authoritative procedure files available to Fleet Ops for the requested language; +- `reported_synced_document_count`, `reported_failed_document_count` and `last_sync_at`: the latest persisted result reported by the central n8n synchronization workflow; +- `document_count`: documents independently verified as indexed by the active provider. + +The current RAGcore contract deliberately has no corpus-size or space-browse endpoint. For +the RAGcore provider, `document_count` therefore remains `null`; a successful upload report +is never relabelled as proof that indexing and publishing completed. The deterministic demo +provider can verify its in-memory corpus and reports `statistics_state=verified`. RAGcore +reports `sync_reported` only when a persisted workflow callback exists, otherwise +`not_reported`. ## Required adapter interface diff --git a/frontend/src/api/types.ts b/frontend/src/api/types.ts index 4180973..3c3bb6e 100644 --- a/frontend/src/api/types.ts +++ b/frontend/src/api/types.ts @@ -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 { diff --git a/frontend/src/i18n/locales/en-GB/dashboard.json b/frontend/src/i18n/locales/en-GB/dashboard.json index 3e6fe47..3047233 100644 --- a/frontend/src/i18n/locales/en-GB/dashboard.json +++ b/frontend/src/i18n/locales/en-GB/dashboard.json @@ -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", diff --git a/frontend/src/i18n/locales/en-GB/integrations.json b/frontend/src/i18n/locales/en-GB/integrations.json index c4a6dd0..7b83f70 100644 --- a/frontend/src/i18n/locales/en-GB/integrations.json +++ b/frontend/src/i18n/locales/en-GB/integrations.json @@ -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", diff --git a/frontend/src/i18n/locales/en-GB/knowledge.json b/frontend/src/i18n/locales/en-GB/knowledge.json index 5fd7958..cc28856 100644 --- a/frontend/src/i18n/locales/en-GB/knowledge.json +++ b/frontend/src/i18n/locales/en-GB/knowledge.json @@ -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", diff --git a/frontend/src/i18n/locales/fr-BE/dashboard.json b/frontend/src/i18n/locales/fr-BE/dashboard.json index e2ca3ec..66d9acd 100644 --- a/frontend/src/i18n/locales/fr-BE/dashboard.json +++ b/frontend/src/i18n/locales/fr-BE/dashboard.json @@ -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 d’index inconnue", + "knowledgeIndexUnknown": "{{count}} sources de connaissances · taille d’index non vérifiable", "knowledgeUnavailable": "Vérification de santé indisponible", "mcpTitle": "MCP Hub", "mcpEnabled": "Enregistrement activé", diff --git a/frontend/src/i18n/locales/fr-BE/integrations.json b/frontend/src/i18n/locales/fr-BE/integrations.json index 77fc94c..92d3d3f 100644 --- a/frontend/src/i18n/locales/fr-BE/integrations.json +++ b/frontend/src/i18n/locales/fr-BE/integrations.json @@ -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 d’index indisponible pour {{collection}}.", + "knowledgeSummaryIndexUnknown": "RAGcore accessible · {{sourceCount}} documents sources dans cette langue · taille d’index non vérifiable pour {{collection}}.", + "knowledgeSummarySyncReported": "RAGcore accessible · {{sourceCount}} sources dans cette langue · dernier rapport : {{synced}} traités, {{failed}} échoués à {{when}} · taille d’index non vérifiable pour {{collection}}.", "knowledgeUnavailable": "Preuves de santé actuellement indisponibles.", "gatewayKicker": "Passerelle d'outils", "mcpTitle": "MCP Hub", diff --git a/frontend/src/i18n/locales/fr-BE/knowledge.json b/frontend/src/i18n/locales/fr-BE/knowledge.json index 426170a..00d78b0 100644 --- a/frontend/src/i18n/locales/fr-BE/knowledge.json +++ b/frontend/src/i18n/locales/fr-BE/knowledge.json @@ -12,6 +12,13 @@ "statusUnavailable": "Indisponible", "proceduresIndexed": "{{count}} procédures indexées", "proceduresIndexUnknown": "La taille de l’index n’est 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 n’expose pas de taille d’index 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 n’expose pas de taille d’index 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", diff --git a/frontend/src/i18n/locales/nl-BE/dashboard.json b/frontend/src/i18n/locales/nl-BE/dashboard.json index 11d5cdf..aa5a4cd 100644 --- a/frontend/src/i18n/locales/nl-BE/dashboard.json +++ b/frontend/src/i18n/locales/nl-BE/dashboard.json @@ -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", diff --git a/frontend/src/i18n/locales/nl-BE/integrations.json b/frontend/src/i18n/locales/nl-BE/integrations.json index 0929231..f25c3d4 100644 --- a/frontend/src/i18n/locales/nl-BE/integrations.json +++ b/frontend/src/i18n/locales/nl-BE/integrations.json @@ -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", diff --git a/frontend/src/i18n/locales/nl-BE/knowledge.json b/frontend/src/i18n/locales/nl-BE/knowledge.json index 78d2b96..e3fcaa7 100644 --- a/frontend/src/i18n/locales/nl-BE/knowledge.json +++ b/frontend/src/i18n/locales/nl-BE/knowledge.json @@ -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", diff --git a/frontend/src/pages/Automation.tsx b/frontend/src/pages/Automation.tsx index 37a2a23..b88a9cf 100644 --- a/frontend/src/pages/Automation.tsx +++ b/frontend/src/pages/Automation.tsx @@ -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, diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index 75bbd01..ea35d69 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -245,7 +245,7 @@ export function Dashboard() {
{t("integrationPulse.knowledgeTitle")} - {knowledge ? (knowledge.document_count === null ? t("integrationPulse.knowledgeIndexUnknown") : t("integrationPulse.knowledgeSummary", { count: knowledge.document_count })) : t("integrationPulse.knowledgeUnavailable")} + {knowledge ? (knowledge.document_count === null ? t("integrationPulse.knowledgeIndexUnknown", { count: knowledge.source_document_count }) : t("integrationPulse.knowledgeSummary", { count: knowledge.document_count })) : t("integrationPulse.knowledgeUnavailable")}
(null); const [statusSettled, setStatusSettled] = useState(false); const [question, setQuestion] = useState(""); @@ -138,6 +140,24 @@ export function Knowledge() { {statusSettled && status?.provider === "demo" && (

{t("providerNote")}

)} + {statusSettled && status?.provider === "ragcore" && ( +
+
{status.source_document_count}{t("statistics.sourceDocuments")}
+
+ {status.reported_synced_document_count ?? "—"} + {t("statistics.reportedSynced")} +
+
+ {status.reported_failed_document_count ?? "—"} + {t("statistics.reportedFailed")} +
+

+ {status.last_sync_at + ? t("statistics.lastSync", { when: formatDateTime(status.last_sync_at) }) + : t("statistics.noSyncReport")} +

+
+ )}
diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 43babcd..0bf4bf0 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -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; }