Download studio/src/components/KnowledgeBase.jsx from shak3008/DiffWeave: direct link, hf CLI and curl.
- Browser
- Download file 59 kB
-
https://huggingface.co/spaces/shak3008/DiffWeave/resolve/main/studio/src/components/KnowledgeBase.jsx
- Command line
-
hf download hf://spaces/shak3008/DiffWeave/studio/src/components/KnowledgeBase.jsx
-
curl -L -o KnowledgeBase.jsx https://huggingface.co/spaces/shak3008/DiffWeave/resolve/main/studio/src/components/KnowledgeBase.jsx
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 (≥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 ≥ 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> | |
| ); | |
| } | |
| } | |