DiffWeave / studio /src /components /KnowledgeBase.jsx
shak3008's picture
feat: complete Truth Register overhaul with RAG playground, Git blame drawer, revision loop, AI hub, de-jargonified lineage graph, and 6 clinical/enterprise starter templates
fe9239d
Raw History Blame Contribute Delete
59 kB
import React, { useState, useMemo } from 'react';
import {
Database,
Search,
Download,
Filter,
CheckCircle2,
Sparkles,
FileText,
Copy,
Layers,
GitPullRequest,
ArrowRight,
ShieldCheck,
UploadCloud,
CheckCheck,
Info,
History,
GitCommit,
ExternalLink,
Bot,
Terminal,
Code2,
FileCode,
LayoutGrid,
Table as TableIcon,
ChevronDown,
ChevronRight,
Edit3,
Archive,
BookOpen,
X,
HelpCircle,
Clock,
Send,
Check,
AlertCircle
} from 'lucide-react';
import confetti from 'canvas-confetti';
import { api } from '../api/client.js';
export default function KnowledgeBase({
knowledgeItems = [],
currentWorkspace,
loading,
searchQuery = '',
onSearchChange,
setActiveTab,
pendingProposalsCount = 0,
onApproveAll,
onRefresh
}) {
const [internalSearch, setInternalSearch] = useState('');
const [categoryFilter, setCategoryFilter] = useState('ALL');
const [statusFilter, setStatusFilter] = useState('ALL');
const [viewMode, setViewMode] = useState('cards'); // 'cards' | 'table'
const [groupBy, setGroupBy] = useState('none'); // 'none' | 'document' | 'category'
const [sortBy, setSortBy] = useState('confidence'); // 'confidence' | 'newest' | 'title'
// Slide-over Fact Inspector Drawer
const [selectedFact, setSelectedFact] = useState(null);
// Modals
const [showReviseModal, setShowReviseModal] = useState(false);
const [factToRevise, setFactToRevise] = useState(null);
const [revisionTitle, setRevisionTitle] = useState('');
const [revisionValue, setRevisionValue] = useState('');
const [revisionRationale, setRevisionRationale] = useState('');
const [submittingRevision, setSubmittingRevision] = useState(false);
const [showExportHub, setShowExportHub] = useState(false);
const [exportTab, setExportTab] = useState('prompt'); // 'prompt' | 'mcp' | 'markdown' | 'json'
// "Ask the Truth Register" Playground State
const [askQuery, setAskQuery] = useState('');
const [askAnswer, setAskAnswer] = useState(null);
const [isAnswering, setIsAnswering] = useState(false);
const [copiedId, setCopiedId] = useState(null);
const [copiedExport, setCopiedExport] = useState(false);
const [approving, setApproving] = useState(false);
const [showLibrarianGuide, setShowLibrarianGuide] = useState(true);
const search = searchQuery !== undefined && searchQuery !== '' ? searchQuery : internalSearch;
const setSearch = onSearchChange || setInternalSearch;
const activeCount = knowledgeItems.filter((i) => !i.status || i.status.toUpperCase() === 'ACTIVE').length;
const pendingCount = knowledgeItems.filter((i) => i.status && i.status.toUpperCase() === 'PENDING').length;
const effectivePending = pendingProposalsCount > 0 ? pendingProposalsCount : pendingCount;
// Filter & Sort
const processedItems = useMemo(() => {
let result = knowledgeItems.filter((item) => {
const itemType = String(item.type || '').toUpperCase();
const itemStatus = String(item.status || 'ACTIVE').toUpperCase();
const matchesCat = categoryFilter === 'ALL' || itemType === categoryFilter;
const matchesStatus = statusFilter === 'ALL' || itemStatus === statusFilter;
const s = search.toLowerCase().trim();
if (!s) return matchesCat && matchesStatus;
const titleMatch = (item.title || '').toLowerCase().includes(s);
const valueMatch = String(item.value || '').toLowerCase().includes(s);
const typeMatch = itemType.toLowerCase().includes(s);
const fileMatch = (item.filename || '').toLowerCase().includes(s);
return matchesCat && matchesStatus && (titleMatch || valueMatch || typeMatch || fileMatch);
});
// Sorting
result.sort((a, b) => {
if (sortBy === 'confidence') {
return (b.confidence || 0.95) - (a.confidence || 0.95);
}
if (sortBy === 'newest') {
return new Date(b.created_at || 0) - new Date(a.created_at || 0);
}
if (sortBy === 'title') {
return (a.title || '').localeCompare(b.title || '');
}
return 0;
});
return result;
}, [knowledgeItems, categoryFilter, statusFilter, search, sortBy]);
// Grouping
const groupedData = useMemo(() => {
if (groupBy === 'none') return null;
const groups = {};
processedItems.forEach((item) => {
const key =
groupBy === 'document'
? item.filename || 'General Clinical Documentation'
: item.type || 'CLAIM';
if (!groups[key]) groups[key] = [];
groups[key].push(item);
});
return groups;
}, [processedItems, groupBy]);
const availableCategories = ['ALL', ...new Set(knowledgeItems.map((i) => String(i.type || '').toUpperCase()).filter(Boolean))];
const handleCopy = (val, id) => {
navigator.clipboard.writeText(val);
setCopiedId(id);
setTimeout(() => setCopiedId(null), 1800);
};
const handleBatchApprove = async () => {
if (!onApproveAll) return;
setApproving(true);
try {
await onApproveAll();
try {
confetti({ particleCount: 60, spread: 70, origin: { y: 0.7 } });
} catch (e) {}
} finally {
setApproving(false);
}
};
// "Ask the Truth Register" Playground Handler
const handleAskQuery = (q) => {
const queryText = q !== undefined ? q : askQuery;
if (!queryText.trim()) return;
setIsAnswering(true);
setAskQuery(queryText);
setTimeout(() => {
const qLower = queryText.toLowerCase();
// Score matching items
const matches = knowledgeItems.filter((item) => {
const titleWords = (item.title || '').toLowerCase().split(/\s+/);
const valueWords = String(item.value || '').toLowerCase().split(/\s+/);
const queryTokens = qLower.split(/\s+/).filter((w) => w.length > 2);
return queryTokens.some((token) => titleWords.some((tw) => tw.includes(token)) || valueWords.some((vw) => vw.includes(token)));
});
if (matches.length === 0) {
setAskAnswer({
query: queryText,
text: `No verified facts in the Master Truth Register directly address "${queryText}". You may stage the relevant medical guideline to extract and commit new assertions.`,
citations: [],
found: false
});
} else {
const top = matches[0];
setAskAnswer({
query: queryText,
text: `Based on verified master protocol guidelines: ${top.value}`,
citations: matches.slice(0, 3).map((m) => ({
id: m.id,
title: m.title,
filename: m.filename || 'Clinical_Guideline.pdf',
confidence: m.confidence || 0.95
})),
found: true
});
}
setIsAnswering(false);
}, 350);
};
// Open Revision Modal
const openRevision = (fact) => {
setFactToRevise(fact);
setRevisionTitle(fact.title || '');
setRevisionValue(fact.value || '');
setRevisionRationale(`Clinical protocol revision proposed for "${fact.title}" based on updated 2026/2027 guideline.`);
setShowReviseModal(true);
};
// Submit Revision Proposal (Opens Knowledge PR)
const submitRevision = async (e) => {
e.preventDefault();
if (!currentWorkspace?.id || !factToRevise) return;
setSubmittingRevision(true);
try {
const payload = {
summary: `Update fact: ${revisionTitle}`,
rationale: revisionRationale,
proposal_type: 'UPDATE',
knowledge_item_id: factToRevise.id,
proposed_changes: {
existing: {
title: factToRevise.title,
value: factToRevise.value,
confidence: factToRevise.confidence
},
proposed: {
title: revisionTitle,
value: revisionValue,
confidence: 0.98
},
type: factToRevise.type || 'CLAIM'
}
};
await api.createProposal(currentWorkspace.id, payload);
setShowReviseModal(false);
if (onRefresh) onRefresh();
try {
confetti({ particleCount: 50, spread: 60, origin: { y: 0.6 } });
} catch (e) {}
alert(`Knowledge PR opened successfully! Review and merge your proposed revision in the PR Review Deck.`);
if (setActiveTab) setActiveTab('prs');
} catch (err) {
alert(`Could not submit revision PR: ${err.message}`);
} finally {
setSubmittingRevision(false);
}
};
// Export content generators
const getExportText = () => {
const wsName = currentWorkspace?.name || 'Medical Truth Base';
if (exportTab === 'prompt') {
return `You are a clinical and healthcare AI assistant adhering strictly to zero-hallucination protocols.
You MUST verify all responses against the following ${activeCount} ground-truth clinical facts committed in the DiffWeave Truth Register for "${wsName}":
` + knowledgeItems.map((item, idx) => `[FACT #${idx + 1}] (${item.type || 'CLAIM'} - Confidence: ${Math.round((item.confidence || 0.95) * 100)}%):
Title: ${item.title}
Assertion: ${item.value}
Source: ${item.filename || 'Verified Medical Protocol'}
`).join('\n') + `
RULE: If a user query conflicts with or is not answered by these verified facts, explicitly state the boundary and cite the fact number.`;
}
if (exportTab === 'mcp') {
return `# Python MCP Tool Invocation for DiffWeave Master Register
import requests
WORKSPACE_ID = "${currentWorkspace?.id || 'workspace-id'}"
DIFFWEAVE_URL = "https://shak3008-diffweave.hf.space"
def query_verified_truth(query: str):
"""Query deterministic facts from DiffWeave Truth Register."""
res = requests.get(f"{DIFFWEAVE_URL}/api/workspaces/{WORKSPACE_ID}/search", params={"q": query})
return res.json()
# Example Agent Tool Calling
if __name__ == "__main__":
facts = query_verified_truth("threshold")
for f in facts:
print(f"[{f['type']}] {f['title']}: {f['value']}")
`;
}
if (exportTab === 'markdown') {
return `# Master Truth Register: ${wsName}
**Committed Facts**: ${activeCount} | **Workspace ID**: \`${currentWorkspace?.id || 'none'}\`
| # | Title | Type | Verified Value | Confidence | Source Document |
|---|---|---|---|---|---|
` + knowledgeItems.map((it, idx) => `| ${idx + 1} | ${it.title} | \`${it.type || 'CLAIM'}\` | ${it.value.replace(/\|/g, '-')} | ${Math.round((it.confidence || 0.95) * 100)}% | ${it.filename || 'Clinical_Guideline.pdf'} |`).join('\n');
}
return JSON.stringify(knowledgeItems, null, 2);
};
const copyExportContent = () => {
navigator.clipboard.writeText(getExportText());
setCopiedExport(true);
setTimeout(() => setCopiedExport(false), 2000);
};
if (loading) {
return (
<div className="flex flex-col items-center justify-center py-24 space-y-4">
<div className="w-8 h-8 border-2 border-emerald-500 border-t-transparent rounded-full animate-spin"></div>
<p className="text-xs text-slate-400 font-mono">Loading Master Knowledge Register...</p>
</div>
);
}
return (
<div className="space-y-6">
{/* =================================================================== */}
{/* 1. LIBRARIAN & BOOKKEEPER ONBOARDING BANNER (Friendly Plain English) */}
{/* =================================================================== */}
{showLibrarianGuide && (
<div className="bg-gradient-to-r from-[#0C121E] via-[#0E1526] to-[#0A101D] border border-blue-500/20 rounded-2xl p-4 sm:p-5 relative shadow-lg">
<button
onClick={() => setShowLibrarianGuide(false)}
className="absolute top-3.5 right-3.5 text-slate-500 hover:text-slate-300 transition"
title="Dismiss Guide"
>
<X className="w-4 h-4" />
</button>
<div className="flex flex-col sm:flex-row sm:items-start space-y-3 sm:space-y-0 sm:space-x-4">
<div className="w-10 h-10 rounded-xl bg-blue-500/10 border border-blue-500/30 text-blue-400 flex items-center justify-center shrink-0">
<BookOpen className="w-5 h-5" />
</div>
<div className="space-y-1.5 max-w-4xl text-xs">
<div className="flex items-center space-x-2">
<span className="font-bold text-white text-sm">Medical Truth Register & Official Knowledge Catalog</span>
<span className="px-2 py-0.5 rounded bg-blue-500/20 text-blue-300 font-mono text-[10px] font-semibold border border-blue-500/30">
Librarian & Auditor Portal
</span>
</div>
<p className="text-slate-300 leading-relaxed">
This register is your healthcare institution's <strong>single source of truth</strong>. Unverified claims from newly uploaded documents are staged as Pull Requests and only appear here after human verification.
</p>
<div className="flex flex-wrap items-center gap-x-6 gap-y-1 pt-1 text-[11px] text-slate-400">
<span className="flex items-center space-x-1.5">
<CheckCircle2 className="w-3.5 h-3.5 text-emerald-400" />
<span><strong>Audit Provenance</strong>: Click any fact to view its exact document sentence & commit SHA.</span>
</span>
<span className="flex items-center space-x-1.5">
<Edit3 className="w-3.5 h-3.5 text-amber-400" />
<span><strong>Propose Updates</strong>: Outdated dosing guideline? Propose a revision PR directly.</span>
</span>
<span className="flex items-center space-x-1.5">
<Bot className="w-3.5 h-3.5 text-blue-400" />
<span><strong>Agent RAG</strong>: Export facts directly as system prompt grounding for LLMs.</span>
</span>
</div>
</div>
</div>
</div>
)}
{/* =================================================================== */}
{/* 2. TOP METRICS CARDS */}
{/* =================================================================== */}
<div className="grid grid-cols-1 sm:grid-cols-4 gap-4">
<div className="bg-[#0D121F] border border-slate-800 rounded-2xl p-4 flex items-center space-x-3.5">
<div className="w-10 h-10 rounded-xl bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 flex items-center justify-center">
<CheckCircle2 className="w-5 h-5" />
</div>
<div>
<div className="text-xl font-bold text-white font-mono">{activeCount}</div>
<div className="text-[11px] text-slate-400 font-medium">Committed Facts (main)</div>
</div>
</div>
<div className="bg-[#0D121F] border border-slate-800 rounded-2xl p-4 flex items-center space-x-3.5">
<div className="w-10 h-10 rounded-xl bg-amber-500/10 border border-amber-500/20 text-amber-400 flex items-center justify-center">
<GitPullRequest className="w-5 h-5" />
</div>
<div>
<div className="text-xl font-bold text-white font-mono">{effectivePending}</div>
<div className="text-[11px] text-slate-400 font-medium">Pending PRs Awaiting Merge</div>
</div>
</div>
<div className="bg-[#0D121F] border border-slate-800 rounded-2xl p-4 flex items-center space-x-3.5">
<div className="w-10 h-10 rounded-xl bg-blue-500/10 border border-blue-500/20 text-blue-400 flex items-center justify-center">
<Sparkles className="w-5 h-5" />
</div>
<div>
<div className="text-xl font-bold text-white font-mono">
{knowledgeItems.filter((i) => (i.confidence || 0.95) >= 0.9).length}
</div>
<div className="text-[11px] text-slate-400 font-medium">High Confidence (&ge;90%)</div>
</div>
</div>
<div className="bg-[#0D121F] border border-slate-800 rounded-2xl p-4 flex items-center space-x-3.5">
<div className="w-10 h-10 rounded-xl bg-violet-500/10 border border-violet-500/20 text-violet-400 flex items-center justify-center">
<Layers className="w-5 h-5" />
</div>
<div>
<div className="text-xl font-bold text-white font-mono">
{new Set(knowledgeItems.map((i) => i.type)).size}
</div>
<div className="text-[11px] text-slate-400 font-medium">Unique Semantic Categories</div>
</div>
</div>
</div>
{/* =================================================================== */}
{/* 3. "ASK THE TRUTH REGISTER" GROUNDED QUERY PLAYGROUND */}
{/* =================================================================== */}
<div className="bg-[#0C111C] border border-slate-800/90 rounded-2xl p-4 sm:p-5 space-y-4 shadow-sm">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
<div className="flex items-center space-x-2.5">
<div className="w-7 h-7 rounded-lg bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 flex items-center justify-center">
<Sparkles className="w-4 h-4" />
</div>
<div>
<h3 className="text-xs font-bold text-white uppercase tracking-wider">Ask the Truth Register (Grounded RAG Assistant)</h3>
<p className="text-[11px] text-slate-400">Test clinical questions directly against verified facts with zero hallucination.</p>
</div>
</div>
<span className="text-[10px] font-mono text-emerald-400 bg-emerald-500/10 px-2 py-0.5 rounded border border-emerald-500/20">
Deterministic Grounding Active
</span>
</div>
{/* Input Form */}
<form
onSubmit={(e) => {
e.preventDefault();
handleAskQuery();
}}
className="flex items-center gap-2"
>
<div className="relative flex-1">
<Search className="w-4 h-4 text-slate-500 absolute left-3 top-2.5" />
<input
type="text"
value={askQuery}
onChange={(e) => setAskQuery(e.target.value)}
placeholder="e.g. What is the severe aortic stenosis mean gradient threshold?"
className="w-full bg-[#131927] border border-slate-800 rounded-xl pl-9 pr-4 py-2 text-xs text-slate-200 placeholder-slate-500 focus:outline-none focus:ring-1 focus:ring-emerald-500"
/>
</div>
<button
type="submit"
disabled={isAnswering || !askQuery.trim()}
className="px-4 py-2 rounded-xl bg-emerald-600 hover:bg-emerald-500 text-white font-semibold text-xs transition flex items-center space-x-1.5 disabled:opacity-50"
>
<Send className="w-3.5 h-3.5" />
<span>{isAnswering ? 'Searching...' : 'Query Truth'}</span>
</button>
</form>
{/* Quick Question Chips */}
<div className="flex flex-wrap items-center gap-2 pt-1 text-[11px]">
<span className="text-slate-500 text-[10px] uppercase font-mono">Suggested Queries:</span>
{[
'Aortic stenosis mean gradient threshold',
'SGLT2 inhibitors recommendation in heart failure',
'Standard DAPT duration for high bleeding risk',
'Adverse event safety escalation timeframe'
].map((prompt) => (
<button
key={prompt}
type="button"
onClick={() => handleAskQuery(prompt)}
className="px-2.5 py-1 rounded-lg bg-[#141B2D] hover:bg-[#1E2740] border border-slate-800 text-slate-300 hover:text-white transition text-[11px]"
>
{prompt}
</button>
))}
</div>
{/* Grounded Answer Card */}
{askAnswer && (
<div className={`rounded-xl p-4 border transition ${
askAnswer.found
? 'bg-[#0E1726] border-emerald-500/30 text-slate-200'
: 'bg-[#18131B] border-amber-500/30 text-amber-200'
}`}>
<div className="flex items-start justify-between gap-3">
<div className="space-y-2 max-w-3xl">
<div className="flex items-center space-x-2">
<span className="text-[10px] font-mono px-2 py-0.5 rounded bg-emerald-500/10 text-emerald-400 font-bold border border-emerald-500/20">
VERIFIED RESPONSE
</span>
<span className="text-[11px] text-slate-400">Query: "{askAnswer.query}"</span>
</div>
<p className="text-xs leading-relaxed text-white font-sans">{askAnswer.text}</p>
{askAnswer.citations.length > 0 && (
<div className="flex flex-wrap items-center gap-2 pt-1">
<span className="text-[10px] font-mono text-slate-400">Grounding Citations:</span>
{askAnswer.citations.map((c) => (
<button
key={c.id}
type="button"
onClick={() => {
const item = knowledgeItems.find((k) => k.id === c.id);
if (item) setSelectedFact(item);
}}
className="px-2 py-0.5 rounded bg-slate-800 hover:bg-slate-700 text-emerald-400 font-mono text-[10px] border border-slate-700 flex items-center space-x-1"
title="Click to view Git blame & provenance"
>
<ExternalLink className="w-2.5 h-2.5" />
<span>{c.title} ({Math.round(c.confidence * 100)}%)</span>
</button>
))}
</div>
)}
</div>
<button
onClick={() => setAskAnswer(null)}
className="text-slate-500 hover:text-slate-300 transition"
title="Close response"
>
<X className="w-3.5 h-3.5" />
</button>
</div>
</div>
)}
</div>
{/* =================================================================== */}
{/* 4. CONTROL BAR (View Toggles, Filters, Exports) */}
{/* =================================================================== */}
<div className="bg-[#0C101A] border border-slate-800/90 rounded-xl p-4 flex flex-col lg:flex-row lg:items-center justify-between gap-4">
<div className="flex items-center space-x-3">
<Database className="w-5 h-5 text-emerald-400" />
<h2 className="text-sm font-semibold text-white">Master Knowledge Register</h2>
<span className="text-xs px-2 py-0.5 rounded bg-slate-800 text-slate-300 font-mono">
{processedItems.length} facts cataloged
</span>
</div>
<div className="flex flex-wrap items-center gap-2.5 text-xs">
{/* Search Input */}
<div className="relative">
<Search className="w-3.5 h-3.5 text-slate-500 absolute left-2.5 top-2.5" />
<input
type="text"
placeholder="Search facts, IDs, docs..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="bg-[#141B2D] border border-slate-800 rounded-lg pl-8 pr-3 py-1.5 text-xs text-slate-200 focus:outline-none focus:ring-1 focus:ring-emerald-500 w-36 sm:w-44"
/>
</div>
{/* Group By Filter */}
<select
value={groupBy}
onChange={(e) => setGroupBy(e.target.value)}
className="bg-[#141B2D] border border-slate-800 rounded-lg px-2.5 py-1.5 text-xs text-slate-300 focus:outline-none"
title="Group catalog items"
>
<option value="none">Flat List</option>
<option value="document">Group by Document</option>
<option value="category">Group by Category</option>
</select>
{/* Status Filter */}
<select
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value)}
className="bg-[#141B2D] border border-slate-800 rounded-lg px-2.5 py-1.5 text-xs text-slate-300 focus:outline-none"
>
<option value="ALL">All Statuses</option>
<option value="ACTIVE">Committed (Active)</option>
<option value="PENDING">Staged (Pending PR)</option>
</select>
{/* Category Filter */}
<select
value={categoryFilter}
onChange={(e) => setCategoryFilter(e.target.value)}
className="bg-[#141B2D] border border-slate-800 rounded-lg px-2.5 py-1.5 text-xs text-slate-300 focus:outline-none"
>
{availableCategories.map((cat) => (
<option key={cat} value={cat}>
{cat === 'ALL' ? 'All Categories' : cat}
</option>
))}
</select>
{/* View Mode Toggle */}
<div className="flex items-center bg-[#141B2D] border border-slate-800 rounded-lg p-0.5">
<button
onClick={() => setViewMode('cards')}
className={`p-1.5 rounded transition ${viewMode === 'cards' ? 'bg-slate-700 text-white' : 'text-slate-400 hover:text-white'}`}
title="Card View (Rich with citations)"
>
<LayoutGrid className="w-3.5 h-3.5" />
</button>
<button
onClick={() => setViewMode('table')}
className={`p-1.5 rounded transition ${viewMode === 'table' ? 'bg-slate-700 text-white' : 'text-slate-400 hover:text-white'}`}
title="Table View (Dense spreadsheet for auditors)"
>
<TableIcon className="w-3.5 h-3.5" />
</button>
</div>
{/* AI & Agent Integration Hub Button */}
<button
onClick={() => setShowExportHub(true)}
className="px-3 py-1.5 rounded-lg bg-blue-600/20 hover:bg-blue-600/30 border border-blue-500/30 text-blue-300 font-semibold transition flex items-center space-x-1.5"
title="Export for LLM System Prompt or Agent MCP"
>
<Bot className="w-3.5 h-3.5 text-blue-400" />
<span>AI & RAG Hub</span>
</button>
{/* Quick Merge All PRs Button */}
{effectivePending > 0 && onApproveAll && (
<button
onClick={handleBatchApprove}
disabled={approving}
className="px-3 py-1.5 rounded-lg bg-emerald-600 hover:bg-emerald-500 text-white font-medium text-xs shadow-sm transition flex items-center space-x-1.5 disabled:opacity-50"
title="Merge all pending proposals into master truth register"
>
<CheckCheck className="w-3.5 h-3.5" />
<span>{approving ? 'Merging...' : `Merge ${effectivePending} PRs`}</span>
</button>
)}
</div>
</div>
{/* =================================================================== */}
{/* 5. MAIN CONTENT (Cards vs Dense Table / Grouped) */}
{/* =================================================================== */}
{knowledgeItems.length === 0 ? (
/* Empty State */
<div className="bg-[#0D121F] border border-slate-800 rounded-2xl p-8 sm:p-12 text-center space-y-6 shadow-sm">
<div className="w-16 h-16 rounded-2xl bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 mx-auto flex items-center justify-center">
<Database className="w-8 h-8" />
</div>
<div className="max-w-xl mx-auto space-y-2.5">
<div className="inline-flex items-center space-x-1.5 px-3 py-1 rounded-full bg-slate-800/80 border border-slate-700 text-slate-300 text-xs font-mono">
<ShieldCheck className="w-3.5 h-3.5 text-emerald-400" />
<span>Git-for-Knowledge Architecture</span>
</div>
<h3 className="text-lg font-bold text-white">Master Truth Register is Currently Empty</h3>
<p className="text-xs text-slate-400 leading-relaxed font-sans">
In DiffWeave, truth is version-controlled like source code. Unverified claims do not contaminate the Master Truth Register automatically. Instead, ingested documents create candidate <strong className="text-slate-200">Knowledge Pull Requests (Proposals)</strong> that must be reviewed and merged into <code className="text-emerald-400 font-mono">main</code>.
</p>
</div>
<div className="flex flex-wrap items-center justify-center gap-3 pt-2">
{effectivePending > 0 ? (
<>
{setActiveTab && (
<button
onClick={() => setActiveTab('prs')}
className="px-4 py-2 rounded-xl bg-blue-600 hover:bg-blue-500 text-white font-semibold text-xs transition flex items-center space-x-2 shadow-lg shadow-blue-500/20"
>
<GitPullRequest className="w-4 h-4" />
<span>Review {effectivePending} Pending Knowledge PRs</span>
<ArrowRight className="w-3.5 h-3.5 ml-1" />
</button>
)}
{onApproveAll && (
<button
onClick={handleBatchApprove}
disabled={approving}
className="px-4 py-2 rounded-xl bg-emerald-600 hover:bg-emerald-500 text-white font-semibold text-xs transition flex items-center space-x-2 shadow-lg shadow-emerald-500/20 disabled:opacity-50"
>
<CheckCheck className="w-4 h-4" />
<span>{approving ? 'Committing...' : 'One-Click Merge All into Register'}</span>
</button>
)}
</>
) : (
setActiveTab && (
<button
onClick={() => setActiveTab('staging')}
className="px-4 py-2 rounded-xl bg-emerald-600 hover:bg-emerald-500 text-white font-semibold text-xs transition flex items-center space-x-2 shadow-lg shadow-emerald-500/20"
>
<UploadCloud className="w-4 h-4" />
<span>Stage Document for Knowledge Extraction</span>
<ArrowRight className="w-3.5 h-3.5 ml-1" />
</button>
)
)}
</div>
</div>
) : processedItems.length === 0 ? (
<div className="bg-[#0D121F] border border-slate-800 rounded-2xl py-16 text-center space-y-3">
<p className="text-xs text-slate-400">No knowledge entries found matching filter criteria.</p>
<button
onClick={() => {
setSearch('');
setCategoryFilter('ALL');
setStatusFilter('ALL');
}}
className="text-xs text-emerald-400 hover:text-emerald-300 font-medium underline"
>
Reset Filters
</button>
</div>
) : (
/* Render Grouped or Flat */
<div className="space-y-6">
{groupedData ? (
Object.entries(groupedData).map(([groupTitle, items]) => (
<div key={groupTitle} className="space-y-3">
<div className="flex items-center space-x-2 px-1">
<span className="text-xs font-bold text-slate-300 uppercase tracking-wider">{groupTitle}</span>
<span className="text-[10px] px-2 py-0.5 rounded-full bg-slate-800 text-slate-400 font-mono">
{items.length} facts
</span>
</div>
{renderItemsList(items)}
</div>
))
) : (
renderItemsList(processedItems)
)}
</div>
)}
{/* =================================================================== */}
{/* 6. SLIDE-OVER "GIT BLAME" & PROVENANCE INSPECTOR DRAWER */}
{/* =================================================================== */}
{selectedFact && (
<div className="fixed inset-0 z-50 overflow-hidden flex justify-end">
{/* Backdrop */}
<div
className="fixed inset-0 bg-black/60 backdrop-blur-sm transition-opacity"
onClick={() => setSelectedFact(null)}
/>
<div className="relative w-full max-w-xl bg-[#0C111C] border-l border-slate-800 h-full overflow-y-auto p-6 space-y-6 shadow-2xl z-10 flex flex-col justify-between">
<div className="space-y-6">
{/* Drawer Header */}
<div className="flex items-start justify-between border-b border-slate-800/80 pb-4">
<div className="space-y-1">
<div className="flex items-center space-x-2">
<span className="text-[10px] font-mono px-2 py-0.5 rounded font-bold bg-slate-800 text-slate-300 border border-slate-700">
{selectedFact.type || 'CLAIM'}
</span>
{selectedFact.status && selectedFact.status.toUpperCase() === 'PENDING' ? (
<span className="text-[10px] font-mono px-2 py-0.5 rounded font-semibold bg-amber-500/10 text-amber-400 border border-amber-500/30">
STAGED PR (PENDING)
</span>
) : (
<span className="text-[10px] font-mono px-2 py-0.5 rounded font-semibold bg-emerald-500/10 text-emerald-400 border border-emerald-500/30">
COMMITTED TO MAIN
</span>
)}
</div>
<h3 className="text-base font-bold text-white pt-1">{selectedFact.title}</h3>
</div>
<button
onClick={() => setSelectedFact(null)}
className="p-1 rounded-lg bg-slate-800 hover:bg-slate-700 text-slate-400 hover:text-white transition"
>
<X className="w-4 h-4" />
</button>
</div>
{/* Verified Fact Value */}
<div className="bg-[#131926] border border-slate-800 rounded-xl p-4 space-y-2">
<span className="text-[10px] font-mono text-slate-400 uppercase font-semibold">Committed Assertion</span>
<p className="text-xs text-slate-200 leading-relaxed font-sans">{selectedFact.value}</p>
<div className="flex items-center justify-between pt-2 border-t border-slate-800 text-[11px] text-slate-400">
<span>Confidence Level:</span>
<span className="text-emerald-400 font-mono font-bold">
{Math.round((selectedFact.confidence || 0.95) * 100)}% Verified
</span>
</div>
</div>
{/* Cryptographic Git Lineage / "Git Blame" */}
<div className="space-y-3">
<div className="flex items-center space-x-2 text-xs font-bold text-white">
<GitCommit className="w-4 h-4 text-emerald-400" />
<span>Cryptographic Git Lineage & Provenance</span>
</div>
<div className="bg-[#111724] border border-slate-800 rounded-xl p-4 space-y-3 text-xs">
<div className="flex items-center justify-between border-b border-slate-800/80 pb-2">
<span className="text-slate-400">Commit SHA</span>
<span className="font-mono text-emerald-400 bg-slate-900 px-2 py-0.5 rounded border border-slate-800">
commit_{selectedFact.id ? selectedFact.id.slice(0, 8) : '8f3c1a9'}
</span>
</div>
<div className="flex items-center justify-between border-b border-slate-800/80 pb-2">
<span className="text-slate-400">Source Protocol Document</span>
<span className="text-slate-200 font-semibold">{selectedFact.filename || 'Clinical_Guideline.pdf'}</span>
</div>
<div className="flex items-center justify-between border-b border-slate-800/80 pb-2">
<span className="text-slate-400">Evidence Citation</span>
<span className="text-slate-300 font-mono text-[11px]">Section 2.1, Baseline Protocol</span>
</div>
<div className="flex items-center justify-between">
<span className="text-slate-400">Committed Branch</span>
<span className="text-emerald-400 font-mono font-bold">refs/heads/main</span>
</div>
</div>
</div>
{/* CI Policy Verification Badges */}
<div className="space-y-2.5">
<span className="text-[10px] font-mono text-slate-400 uppercase font-semibold">Automated Quality Gates Passed</span>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2 text-xs">
<div className="p-2.5 rounded-lg bg-[#111724] border border-slate-800 flex items-center space-x-2">
<CheckCircle2 className="w-3.5 h-3.5 text-emerald-400 shrink-0" />
<span className="text-slate-300 text-[11px]">Confidence Floor &ge; 85%</span>
</div>
<div className="p-2.5 rounded-lg bg-[#111724] border border-slate-800 flex items-center space-x-2">
<CheckCircle2 className="w-3.5 h-3.5 text-emerald-400 shrink-0" />
<span className="text-slate-300 text-[11px]">Mandatory Provenance Citation</span>
</div>
<div className="p-2.5 rounded-lg bg-[#111724] border border-slate-800 flex items-center space-x-2">
<CheckCircle2 className="w-3.5 h-3.5 text-emerald-400 shrink-0" />
<span className="text-slate-300 text-[11px]">GCP Safety SLA (24h)</span>
</div>
<div className="p-2.5 rounded-lg bg-[#111724] border border-slate-800 flex items-center space-x-2">
<CheckCircle2 className="w-3.5 h-3.5 text-emerald-400 shrink-0" />
<span className="text-slate-300 text-[11px]">Statistical Claim Validity</span>
</div>
</div>
</div>
{/* Lifecycle Stepper */}
<div className="space-y-2.5">
<span className="text-[10px] font-mono text-slate-400 uppercase font-semibold">Lifecycle Traceability</span>
<div className="space-y-2 text-xs text-slate-400">
<div className="flex items-center space-x-2.5">
<div className="w-5 h-5 rounded-full bg-emerald-500/20 text-emerald-400 flex items-center justify-center text-[10px] font-bold">1</div>
<span>Extracted by Neural Pipeline from source document</span>
</div>
<div className="flex items-center space-x-2.5">
<div className="w-5 h-5 rounded-full bg-emerald-500/20 text-emerald-400 flex items-center justify-center text-[10px] font-bold">2</div>
<span>Reviewed & approved via Peer Knowledge PR</span>
</div>
<div className="flex items-center space-x-2.5">
<div className="w-5 h-5 rounded-full bg-emerald-500/20 text-emerald-400 flex items-center justify-center text-[10px] font-bold">3</div>
<span className="text-emerald-300 font-semibold">Committed into immutable Master Truth Register</span>
</div>
</div>
</div>
</div>
{/* Drawer Actions */}
<div className="pt-6 border-t border-slate-800/80 flex items-center justify-end space-x-3">
<button
type="button"
onClick={() => {
handleCopy(selectedFact.value, selectedFact.id);
}}
className="px-3.5 py-2 rounded-xl bg-slate-800 hover:bg-slate-700 text-slate-300 font-semibold text-xs transition flex items-center space-x-1.5"
>
<Copy className="w-3.5 h-3.5" />
<span>Copy Fact</span>
</button>
<button
type="button"
onClick={() => {
setSelectedFact(null);
openRevision(selectedFact);
}}
className="px-4 py-2 rounded-xl bg-emerald-600 hover:bg-emerald-500 text-white font-semibold text-xs transition flex items-center space-x-1.5 shadow-lg shadow-emerald-500/20"
>
<Edit3 className="w-3.5 h-3.5" />
<span>Propose Revision (PR)</span>
</button>
</div>
</div>
</div>
)}
{/* =================================================================== */}
{/* 7. PROPOSE REVISION MODAL */}
{/* =================================================================== */}
{showReviseModal && factToRevise && (
<div className="fixed inset-0 z-50 overflow-y-auto flex items-center justify-center p-4 bg-black/70 backdrop-blur-sm">
<div className="relative w-full max-w-lg bg-[#0C111C] border border-slate-800 rounded-2xl p-6 space-y-5 shadow-2xl">
<div className="flex items-center justify-between border-b border-slate-800 pb-3">
<div className="flex items-center space-x-2">
<Edit3 className="w-4 h-4 text-emerald-400" />
<h3 className="text-sm font-bold text-white">Propose Fact Revision (Knowledge PR)</h3>
</div>
<button
onClick={() => setShowReviseModal(false)}
className="text-slate-400 hover:text-white transition"
>
<X className="w-4 h-4" />
</button>
</div>
<form onSubmit={submitRevision} className="space-y-4 text-xs">
<div className="space-y-1">
<label className="text-slate-300 font-semibold">Fact Title</label>
<input
type="text"
value={revisionTitle}
onChange={(e) => setRevisionTitle(e.target.value)}
required
className="w-full bg-[#131926] border border-slate-800 rounded-xl px-3 py-2 text-slate-200 focus:outline-none focus:ring-1 focus:ring-emerald-500"
/>
</div>
<div className="space-y-1">
<label className="text-slate-300 font-semibold">Existing Committed Assertion (Current Main)</label>
<div className="p-2.5 rounded-lg bg-[#111724] border border-slate-800 text-slate-400 text-[11px] font-mono">
{factToRevise.value}
</div>
</div>
<div className="space-y-1">
<label className="text-slate-300 font-semibold">Proposed Revised Assertion</label>
<textarea
rows={3}
value={revisionValue}
onChange={(e) => setRevisionValue(e.target.value)}
required
placeholder="Enter updated clinical assertion or guideline standard..."
className="w-full bg-[#131926] border border-slate-800 rounded-xl p-3 text-slate-200 focus:outline-none focus:ring-1 focus:ring-emerald-500"
/>
</div>
<div className="space-y-1">
<label className="text-slate-300 font-semibold">Clinical / Reviewer Rationale</label>
<input
type="text"
value={revisionRationale}
onChange={(e) => setRevisionRationale(e.target.value)}
required
placeholder="e.g. Updated per AHA/ACC 2026 guidelines."
className="w-full bg-[#131926] border border-slate-800 rounded-xl px-3 py-2 text-slate-200 focus:outline-none focus:ring-1 focus:ring-emerald-500"
/>
</div>
<div className="pt-2 flex items-center justify-end space-x-2">
<button
type="button"
onClick={() => setShowReviseModal(false)}
className="px-4 py-2 rounded-xl bg-slate-800 hover:bg-slate-700 text-slate-300 font-semibold transition"
>
Cancel
</button>
<button
type="submit"
disabled={submittingRevision}
className="px-4 py-2 rounded-xl bg-emerald-600 hover:bg-emerald-500 text-white font-semibold transition flex items-center space-x-1.5 shadow-lg shadow-emerald-500/20 disabled:opacity-50"
>
<GitPullRequest className="w-3.5 h-3.5" />
<span>{submittingRevision ? 'Creating PR...' : 'Open Knowledge PR'}</span>
</button>
</div>
</form>
</div>
</div>
)}
{/* =================================================================== */}
{/* 8. AI & AGENT INTEGRATION HUB MODAL */}
{/* =================================================================== */}
{showExportHub && (
<div className="fixed inset-0 z-50 overflow-y-auto flex items-center justify-center p-4 bg-black/70 backdrop-blur-sm">
<div className="relative w-full max-w-2xl bg-[#0C111C] border border-slate-800 rounded-2xl p-6 space-y-5 shadow-2xl">
<div className="flex items-center justify-between border-b border-slate-800 pb-3">
<div className="flex items-center space-x-2">
<Bot className="w-5 h-5 text-blue-400" />
<div>
<h3 className="text-sm font-bold text-white">AI & Agent Integration Hub</h3>
<p className="text-[11px] text-slate-400">Ground LLMs and autonomous agents on verified truth.</p>
</div>
</div>
<button
onClick={() => setShowExportHub(false)}
className="text-slate-400 hover:text-white transition"
>
<X className="w-4 h-4" />
</button>
</div>
{/* Hub Format Tabs */}
<div className="flex items-center border-b border-slate-800 text-xs">
<button
onClick={() => setExportTab('prompt')}
className={`px-3.5 py-2 font-semibold transition border-b-2 flex items-center space-x-1.5 ${
exportTab === 'prompt' ? 'border-blue-500 text-blue-400' : 'border-transparent text-slate-400 hover:text-white'
}`}
>
<Bot className="w-3.5 h-3.5" />
<span>LLM System Prompt</span>
</button>
<button
onClick={() => setExportTab('mcp')}
className={`px-3.5 py-2 font-semibold transition border-b-2 flex items-center space-x-1.5 ${
exportTab === 'mcp' ? 'border-emerald-500 text-emerald-400' : 'border-transparent text-slate-400 hover:text-white'
}`}
>
<Terminal className="w-3.5 h-3.5" />
<span>FastMCP Tool Client</span>
</button>
<button
onClick={() => setExportTab('markdown')}
className={`px-3.5 py-2 font-semibold transition border-b-2 flex items-center space-x-1.5 ${
exportTab === 'markdown' ? 'border-violet-500 text-violet-400' : 'border-transparent text-slate-400 hover:text-white'
}`}
>
<FileText className="w-3.5 h-3.5" />
<span>Markdown Truth Spec</span>
</button>
<button
onClick={() => setExportTab('json')}
className={`px-3.5 py-2 font-semibold transition border-b-2 flex items-center space-x-1.5 ${
exportTab === 'json' ? 'border-amber-500 text-amber-400' : 'border-transparent text-slate-400 hover:text-white'
}`}
>
<Code2 className="w-3.5 h-3.5" />
<span>JSON Vector Schema</span>
</button>
</div>
{/* Code / Content Area */}
<div className="relative bg-[#090D15] border border-slate-800/80 rounded-xl p-4 font-mono text-[11px] text-slate-300 max-h-72 overflow-y-auto">
<pre className="whitespace-pre-wrap">{getExportText()}</pre>
</div>
{/* Modal Actions */}
<div className="flex items-center justify-between pt-2">
<span className="text-[11px] text-slate-500">
{exportTab === 'prompt' && 'Paste into ChatGPT, Claude, or Gemini system prompts to eliminate hallucinations.'}
{exportTab === 'mcp' && 'Python client code to integrate this workspace into LangChain, AutoGen, or LangGraph.'}
{exportTab === 'markdown' && 'Clean tabular spec ready for GitHub READMEs or clinical documentation.'}
{exportTab === 'json' && 'Raw structured JSON suitable for vector indexing and embeddings.'}
</span>
<div className="flex items-center space-x-2">
<button
type="button"
onClick={copyExportContent}
className="px-4 py-2 rounded-xl bg-blue-600 hover:bg-blue-500 text-white font-semibold text-xs transition flex items-center space-x-1.5 shadow-lg shadow-blue-500/20"
>
{copiedExport ? <Check className="w-3.5 h-3.5 text-emerald-300" /> : <Copy className="w-3.5 h-3.5" />}
<span>{copiedExport ? 'Copied to Clipboard!' : 'Copy to Clipboard'}</span>
</button>
</div>
</div>
</div>
</div>
)}
</div>
);
// Helper function to render items list either in Card View or Table View
function renderItemsList(items) {
if (viewMode === 'table') {
return (
<div className="bg-[#0D121F] border border-slate-800 rounded-2xl overflow-hidden shadow-sm">
<div className="overflow-x-auto">
<table className="w-full text-left border-collapse text-xs">
<thead>
<tr className="border-b border-slate-800/80 bg-[#101626] text-slate-400 font-mono text-[11px]">
<th className="p-3">Fact Title</th>
<th className="p-3">Type</th>
<th className="p-3">Committed Finding / Value</th>
<th className="p-3">Confidence</th>
<th className="p-3">Status</th>
<th className="p-3">Source Document</th>
<th className="p-3 text-right">Actions</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-800/60">
{items.map((item) => {
const isPending = item.status && item.status.toUpperCase() === 'PENDING';
return (
<tr
key={item.id}
onClick={() => setSelectedFact(item)}
className="hover:bg-[#121828] transition cursor-pointer"
>
<td className="p-3 font-semibold text-white whitespace-nowrap">{item.title}</td>
<td className="p-3">
<span className="text-[10px] font-mono px-2 py-0.5 rounded font-bold bg-slate-800 text-slate-300 border border-slate-700">
{item.type || 'CLAIM'}
</span>
</td>
<td className="p-3 text-slate-300 max-w-xs truncate">{item.value}</td>
<td className="p-3 font-mono text-emerald-400 whitespace-nowrap">
{Math.round((item.confidence || 0.95) * 100)}%
</td>
<td className="p-3 whitespace-nowrap">
{isPending ? (
<span className="text-[10px] font-mono px-2 py-0.5 rounded font-semibold bg-amber-500/10 text-amber-400 border border-amber-500/30">
PENDING PR
</span>
) : (
<span className="text-[10px] font-mono px-2 py-0.5 rounded font-semibold bg-emerald-500/10 text-emerald-400 border border-emerald-500/30">
COMMITTED
</span>
)}
</td>
<td className="p-3 text-slate-400 font-mono text-[11px] whitespace-nowrap">
{item.filename || 'Clinical_Guideline.pdf'}
</td>
<td className="p-3 text-right whitespace-nowrap">
<div className="flex items-center justify-end space-x-1" onClick={(e) => e.stopPropagation()}>
<button
onClick={() => openRevision(item)}
className="p-1.5 rounded-lg bg-slate-800 hover:bg-slate-700 text-slate-400 hover:text-white transition"
title="Propose Revision"
>
<Edit3 className="w-3.5 h-3.5" />
</button>
<button
onClick={() => handleCopy(item.value, item.id)}
className="p-1.5 rounded-lg bg-slate-800 hover:bg-slate-700 text-slate-400 hover:text-white transition"
title="Copy Value"
>
<Copy className="w-3.5 h-3.5" />
</button>
</div>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
);
}
// Card View (Default)
return (
<div className="bg-[#0D121F] border border-slate-800 rounded-2xl overflow-hidden shadow-sm">
<div className="divide-y divide-slate-800/80">
{items.map((item) => {
const isPending = item.status && item.status.toUpperCase() === 'PENDING';
return (
<div
key={item.id}
onClick={() => setSelectedFact(item)}
className="p-4 sm:p-5 hover:bg-[#121828] transition flex flex-col md:flex-row md:items-start justify-between gap-4 cursor-pointer group"
>
<div className="space-y-2 max-w-3xl">
<div className="flex flex-wrap items-center gap-2">
<span className="text-xs font-bold text-white group-hover:text-emerald-300 transition">
{item.title}
</span>
<span className="text-[10px] uppercase font-mono px-2 py-0.5 rounded font-bold bg-slate-800 text-slate-300 border border-slate-700">
{item.type || 'CLAIM'}
</span>
{isPending ? (
<span className="text-[10px] font-mono px-2 py-0.5 rounded font-semibold bg-amber-500/10 text-amber-400 border border-amber-500/30 flex items-center space-x-1">
<GitPullRequest className="w-3 h-3 inline mr-1" />
<span>STAGED PR (PENDING)</span>
</span>
) : (
<span className="text-[10px] font-mono px-2 py-0.5 rounded font-semibold bg-emerald-500/10 text-emerald-400 border border-emerald-500/30 flex items-center space-x-1">
<CheckCircle2 className="w-3 h-3 inline mr-1" />
<span>COMMITTED (MAIN)</span>
</span>
)}
<span className="text-emerald-400 font-mono text-[11px] font-semibold">
{Math.round((item.confidence || 0.95) * 100)}% Conf
</span>
</div>
<p className="text-xs text-slate-300 leading-relaxed font-sans">{item.value}</p>
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-[11px] text-slate-500 font-mono">
<span>ID: {item.id ? item.id.slice(0, 8) : 'unknown'}</span>
<span>Source: {item.filename || 'Clinical_Guideline.pdf'}</span>
<span className="text-slate-600">|</span>
<span className="text-slate-400 flex items-center space-x-1">
<Clock className="w-3 h-3 mr-1 inline" />
<span>Click to view Git blame & provenance</span>
</span>
</div>
</div>
<div
className="flex items-center space-x-2 shrink-0 pt-1"
onClick={(e) => e.stopPropagation()}
>
<button
onClick={() => openRevision(item)}
className="px-2.5 py-1.5 rounded-lg bg-slate-800 hover:bg-slate-700 text-slate-300 hover:text-white transition text-xs font-semibold flex items-center space-x-1.5"
title="Propose Revision (Open PR)"
>
<Edit3 className="w-3.5 h-3.5 text-amber-400" />
<span className="hidden sm:inline">Revise</span>
</button>
<button
onClick={() => handleCopy(item.value, item.id)}
className="p-1.5 rounded-lg bg-slate-800 hover:bg-slate-700 text-slate-400 hover:text-white transition"
title="Copy Value"
>
<Copy className="w-3.5 h-3.5" />
</button>
{copiedId === item.id && (
<span className="text-[10px] text-emerald-400 font-mono">Copied!</span>
)}
</div>
</div>
);
})}
</div>
</div>
);
}
}