import { useState, useRef, useCallback } from "react"; const API_BASE = ""; const SEV_COLOR = { high: "#ff4d6d", medium: "#ff9f1c", low: "#2ec4b6" }; const SEV_BG = { high: "#fff0f3", medium: "#fff8ee", low: "#f0fafa" }; function Badge({ sev }) { const col = SEV_COLOR[sev] || "#888"; return ( {sev} ); } function StatCard({ value, label, color, bg }) { return (
{value}
{label}
); } export default function App() { const [files, setFiles] = useState([]); const [dragging, setDragging] = useState(false); const [loading, setLoading] = useState(false); const [progress, setProgress] = useState(0); const [statusMsg, setStatusMsg] = useState(""); const [results, setResults] = useState(null); const [error, setError] = useState(null); const [activeTab, setActiveTab] = useState("ranked"); const fileRef = useRef(); const addFiles = useCallback((incoming) => { const pdfs = Array.from(incoming).filter(f => f.type === "application/pdf"); if (pdfs.length < incoming.length) { setError("Only PDF files are supported."); } setFiles(prev => { const existing = new Set(prev.map(f => f.name)); return [...prev, ...pdfs.filter(f => !existing.has(f.name))]; }); }, []); const removeFile = (name) => setFiles(f => f.filter(x => x.name !== name)); const reset = () => { setFiles([]); setResults(null); setError(null); setProgress(0); setStatusMsg(""); setActiveTab("ranked"); }; const analyze = async () => { if (files.length < 2) { setError("Please upload at least 2 PDF files."); return; } setError(null); setLoading(true); setProgress(10); setResults(null); // Fake progress while waiting const ticker = setInterval(() => { setProgress(p => p < 85 ? p + 3 : p); }, 1200); const msgs = [ "Extracting text from PDFs...", "Sending to Groq for protocol extraction...", "Comparing protocols across papers...", "Generating contradiction report...", ]; let mi = 0; setStatusMsg(msgs[mi]); const msgTicker = setInterval(() => { mi = Math.min(mi + 1, msgs.length - 1); setStatusMsg(msgs[mi]); }, 6000); try { const form = new FormData(); files.forEach(f => form.append("files", f)); const resp = await fetch(`${API_BASE}/analyze`, { method: "POST", body: form }); if (!resp.ok) { const err = await resp.json().catch(() => ({})); throw new Error(err.detail || `Server error ${resp.status}`); } const data = await resp.json(); setProgress(100); setStatusMsg("Analysis complete!"); setResults(data); setActiveTab("ranked"); } catch (e) { setError(e.message); } finally { clearInterval(ticker); clearInterval(msgTicker); setLoading(false); } }; const downloadReport = () => { if (!results?.html_report) return; const blob = new Blob([results.html_report], { type: "text/html" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = "protocol_report.html"; a.click(); URL.revokeObjectURL(url); }; const comp = results?.comparison || {}; const summary = comp.summary || {}; const contras = comp.contradictions || []; const ranked = comp.ranked_issues || []; const optimal = comp.optimal_protocol || {}; const paperNames = results?.paper_names || []; const extracted = results?.extracted || []; return (
{/* ── Header ── */}
ContraGenAI · Bioengineering · Reproducibility

ContraGenAI

A Protocol Contradiction Detector

Upload 2 or more research papers and detect methodological contradictions across experimental protocols — reagents, temperatures, cell lines, antibodies, timing and more. ContraGenAI · Powered by Groq + LLaMA 3.3 70B

{/* ── Upload section ── */} {!results && (
Upload Research Papers
{/* Drop zone */}
fileRef.current?.click()} onDragOver={e => { e.preventDefault(); setDragging(true); }} onDragLeave={() => setDragging(false)} onDrop={e => { e.preventDefault(); setDragging(false); addFiles(e.dataTransfer.files); }} style={{ border: `2px dashed ${dragging ? "#7b2d8b" : "#ddd"}`, borderRadius: 10, padding: "36px 24px", textAlign: "center", cursor: "pointer", background: dragging ? "#f5f0ff" : "#fafafa", transition: "all .2s", marginBottom: 16, }} > addFiles(e.target.files)} />
📄
Drag and drop PDFs here
or click to browse · multiple files supported
{/* File list */} {files.length > 0 && (
{files.map(f => (
📄 {f.name} {(f.size / 1024).toFixed(0)} KB
))}
{files.length} file(s) ready
)} {/* Error */} {error && (
⚠ {error}
)} {/* Progress */} {loading && (
{statusMsg} {progress}%
)} {/* Analyze button */} {files.length < 2 && !loading && ( Add {2 - files.length} more paper{files.length === 1 ? "" : "s"} to start )}
)} {/* ── Results ── */} {results && ( <> {/* Results header */}
Analysis Results
{paperNames.length} papers · {comp.protocol_type || "Protocol"}
{/* Summary stats */}
{/* Tab nav */}
{[ { id: "ranked", label: "Ranked Issues" }, { id: "table", label: "Comparison Table" }, { id: "optimal", label: "Optimal Protocol" }, { id: "extraction", label: "Extraction Summary" }, ].map(t => ( ))}
{/* Tab: Ranked */} {activeTab === "ranked" && (
{ranked.length === 0 ?

No ranked issues found.

: ranked.map((item, i) => { const sev = item.severity || "low"; const col = SEV_COLOR[sev] || "#888"; const bg = SEV_BG[sev] || "#fafafa"; return (
#{item.rank}
{item.parameter}
{item.brief}
); }) }
)} {/* Tab: Comparison Table */} {activeTab === "table" && (
{contras.length === 0 ?

No contradictions found.

: ( {["Parameter", "Category", "Severity", ...paperNames.map((n, i) => `Paper ${i + 1}`), "Impact on Reproducibility" ].map(h => ( ))} {contras.map((c, i) => { const sev = c.severity || "low"; const col = SEV_COLOR[sev] || "#888"; return ( {paperNames.map((_, j) => ( ))} ); })}
{h}
{c.parameter} {c.category} {c.values?.[`paper_${j}`] || "—"} {c.explanation}
) }
)} {/* Tab: Optimal Protocol */} {activeTab === "optimal" && (

{optimal.rationale}

{(optimal.parameters || []).map((p, i) => (
{p.label}
{p.value}
{p.reason}
))}
)} {/* Tab: Extraction Summary */} {activeTab === "extraction" && (
{extracted.map((e, i) => { const cats = e.parameters || {}; const total = Object.values(cats).reduce((s, v) => s + (Array.isArray(v) ? v.length : 0), 0); return (
Paper {i + 1}: {e.title || e._filename}
{e.authors}  |  {e.year}  |  {e.protocol_type}
{total} parameters extracted across {Object.keys(cats).length} categories
); })}
)} )}
); }