"use client"; import { useState, useMemo, useCallback } from "react"; interface GraphNode { id: string; label: string; type: string; x: number; y: number; description?: string; } interface GraphEdge { source: string; target: string; label: string; } interface Scenario { name: string; query: string; nodes: GraphNode[]; edges: GraphEdge[]; reasoning: string[]; } const TYPE_COLORS: Record = { PERSON: "#FF6B6B", ORGANIZATION: "#4ECDC4", LOCATION: "#45B7D1", MOLECULE: "#A29BFE", ORGANELLE: "#55EFC4", CONCEPT: "#AED6F1", PROCESS: "#F9CA24", CONSTANT: "#FD79A8", ELEMENT: "#74B9FF", QUERY: "#FF6B00", }; // ── 4 Science Scenarios ──────────────────────────────────────────────────── const SCENARIOS: Scenario[] = [ { name: "General Relativity", query: "How does General Relativity predict gravitational waves?", nodes: [ { id: "q", label: "Query", type: "QUERY", x: 450, y: 260, description: "Entry point — identify key entities and traverse the graph" }, { id: "einstein", label: "Albert Einstein", type: "PERSON", x: 200, y: 120, description: "Theoretical physicist; developed General Relativity (1915)" }, { id: "gr", label: "General Relativity", type: "CONCEPT", x: 680, y: 120, description: "Geometric theory of gravitation; gravity = spacetime curvature" }, { id: "spacetime", label: "Spacetime Curvature",type: "CONCEPT", x: 450, y: 90, description: "4D manifold warped by mass and energy — the mechanism of gravity" }, { id: "grav_waves", label: "Gravitational Waves",type: "CONCEPT", x: 790, y: 270, description: "Ripples in spacetime produced by accelerating masses; predicted 1916" }, { id: "black_holes",label: "Black Holes", type: "CONCEPT", x: 700, y: 420, description: "Regions where spacetime curvature prevents light escape; GR prediction" }, { id: "ligo", label: "LIGO Detector", type: "ORGANIZATION", x: 450, y: 440, description: "Detected gravitational waves 14 Sep 2015 — confirmed GR's prediction" }, { id: "eddington", label: "Eddington (1919)", type: "PERSON", x: 160, y: 320, description: "Observed light bending around the Sun during 1919 eclipse — first GR proof" }, { id: "gps", label: "GPS Satellites", type: "CONCEPT", x: 160, y: 430, description: "Require GR time-dilation corrections; practical proof of the theory" }, ], edges: [ { source: "q", target: "einstein", label: "FOUND_ENTITY" }, { source: "q", target: "gr", label: "FOUND_ENTITY" }, { source: "einstein", target: "gr", label: "DEVELOPED_1915" }, { source: "einstein", target: "spacetime", label: "PROPOSED" }, { source: "gr", target: "spacetime", label: "DESCRIBES" }, { source: "gr", target: "grav_waves", label: "PREDICTS" }, { source: "gr", target: "black_holes",label: "PREDICTS" }, { source: "grav_waves", target: "ligo", label: "DETECTED_BY" }, { source: "eddington", target: "gr", label: "CONFIRMED_1919" }, { source: "gr", target: "gps", label: "CORRECTION_REQUIRED_BY" }, ], reasoning: [ "Entry: Query identifies Einstein and General Relativity as key entities", "Hop 1: DEVELOPED_1915 → Einstein proposed spacetime curvature as gravity", "Hop 2: PREDICTS edges → GR implies gravitational waves and black holes", "Hop 3: DETECTED_BY → LIGO confirmed waves 100 years after prediction", ], }, { name: "DNA → Protein", query: "How does DNA encode and produce proteins?", nodes: [ { id: "q", label: "Query", type: "QUERY", x: 450, y: 260, description: "Multi-hop biology question — trace the central dogma pathway" }, { id: "dna", label: "DNA", type: "MOLECULE", x: 200, y: 130, description: "Double-helix polymer; stores genetic instructions via A-T-G-C base pairs" }, { id: "rna", label: "mRNA", type: "MOLECULE", x: 700, y: 130, description: "Messenger RNA; transcribed copy of a gene, carries code to ribosome" }, { id: "protein", label: "Protein", type: "MOLECULE", x: 450, y: 420, description: "Amino-acid chain folded into functional shape; performs cellular work" }, { id: "watson_crick", label: "Watson & Crick", type: "PERSON", x: 80, y: 200, description: "Determined DNA double-helix structure (1953) using Franklin's X-ray data" }, { id: "helicase", label: "Helicase", type: "MOLECULE", x: 200, y: 370, description: "Enzyme that unwinds the DNA double helix during replication/transcription" }, { id: "ribosome", label: "Ribosome", type: "ORGANELLE",x: 700, y: 370, description: "Molecular machine that reads mRNA codons and assembles amino acids into protein" }, { id: "nucleus", label: "Cell Nucleus", type: "ORGANELLE",x: 200, y: 260, description: "DNA is stored here; transcription (DNA→mRNA) occurs inside" }, { id: "central_dogma",label: "Central Dogma", type: "CONCEPT", x: 450, y: 110, description: "Information flow: DNA → RNA → Protein (Crick, 1958)" }, ], edges: [ { source: "q", target: "dna", label: "FOUND_ENTITY" }, { source: "q", target: "protein", label: "FOUND_ENTITY" }, { source: "watson_crick", target: "dna", label: "DISCOVERED_1953" }, { source: "dna", target: "central_dogma", label: "DESCRIBED_BY" }, { source: "dna", target: "nucleus", label: "LOCATED_IN" }, { source: "helicase", target: "dna", label: "UNWINDS" }, { source: "dna", target: "rna", label: "TRANSCRIBED_TO" }, { source: "rna", target: "ribosome", label: "READ_BY" }, { source: "ribosome", target: "protein", label: "PRODUCES" }, { source: "central_dogma",target: "rna", label: "INCLUDES" }, ], reasoning: [ "Entry: Two key entities — DNA (information store) and Protein (output)", "Hop 1: TRANSCRIBED_TO — DNA → mRNA; helicase unwinds the double helix", "Hop 2: READ_BY — mRNA travels to ribosome in the cytoplasm", "Hop 3: PRODUCES — Ribosome assembles amino acids into the final protein", ], }, { name: "Photosynthesis", query: "What converts sunlight to glucose in plants?", nodes: [ { id: "q", label: "Query", type: "QUERY", x: 450, y: 260, description: "Factoid + multi-hop: identify the process and trace its pathway" }, { id: "photosynthesis",label:"Photosynthesis", type: "PROCESS", x: 450, y: 110, description: "Converts light energy + CO₂ + H₂O → glucose + O₂; primary energy source for life" }, { id: "chlorophyll", label: "Chlorophyll", type: "MOLECULE",x: 200, y: 140, description: "Green pigment in chloroplasts; absorbs red (~680 nm) and blue (~430 nm) light" }, { id: "light", label: "Light Energy", type: "CONCEPT", x: 80, y: 260, description: "Solar radiation — the energy input that drives the entire process" }, { id: "calvin_cycle", label: "Calvin Cycle", type: "PROCESS", x: 720, y: 190, description: "Light-independent reactions in stroma; uses ATP + NADPH to fix CO₂ into glucose" }, { id: "glucose", label: "Glucose (C₆H₁₂O₆)",type:"MOLECULE",x:720, y: 370, description: "6-carbon sugar; stores chemical energy for the plant and food chain" }, { id: "co2", label: "CO₂", type: "MOLECULE",x: 450, y: 430, description: "Carbon dioxide; fixed by RuBisCO enzyme in the Calvin Cycle" }, { id: "water", label: "H₂O", type: "MOLECULE",x: 200, y: 370, description: "Split by photolysis in thylakoids; provides electrons and releases O₂" }, { id: "oxygen", label: "O₂ (byproduct)", type: "MOLECULE",x: 80, y: 400, description: "Released during photolysis of water — the origin of Earth's atmospheric oxygen" }, { id: "thylakoid", label: "Thylakoid", type: "ORGANELLE",x:350, y: 370, description: "Membrane system inside chloroplast; site of light-dependent reactions" }, ], edges: [ { source: "q", target: "photosynthesis",label: "FOUND_ENTITY" }, { source: "q", target: "chlorophyll", label: "FOUND_ENTITY" }, { source: "light", target: "chlorophyll", label: "ABSORBED_BY" }, { source: "chlorophyll", target: "photosynthesis",label: "DRIVES" }, { source: "water", target: "photosynthesis",label: "INPUT" }, { source: "water", target: "oxygen", label: "PHOTOLYSIS_PRODUCES" }, { source: "co2", target: "calvin_cycle", label: "FIXED_BY" }, { source: "photosynthesis",target: "calvin_cycle", label: "INCLUDES" }, { source: "calvin_cycle", target: "glucose", label: "PRODUCES" }, { source: "thylakoid", target: "photosynthesis",label: "LOCATION_OF" }, ], reasoning: [ "Entry: Photosynthesis and Chlorophyll identified as primary entities", "Hop 1: ABSORBED_BY — light energy absorbed by chlorophyll in thylakoids", "Hop 2: INCLUDES — photosynthesis triggers Calvin Cycle with CO₂ as input", "Hop 3: PRODUCES — Calvin Cycle outputs glucose; water photolysis releases O₂", ], }, { name: "Quantum Mechanics Founders", query: "Which physicists developed quantum mechanics and what did each contribute?", nodes: [ { id: "q", label: "Query", type: "QUERY", x: 450, y: 260, description: "Multi-hop comparison — identify multiple entities and their relationships" }, { id: "qm", label: "Quantum Mechanics", type: "CONCEPT", x: 450, y: 110, description: "Physics of matter at atomic/subatomic scales; emerged from failures of classical physics" }, { id: "bohr", label: "Niels Bohr", type: "PERSON", x: 180, y: 150, description: "Proposed quantized electron orbits (1913 Bohr model); founded Copenhagen interpretation" }, { id: "heisenberg", label: "Heisenberg", type: "PERSON", x: 720, y: 150, description: "Formulated matrix mechanics (1925) and the uncertainty principle (1927)" }, { id: "schrodinger", label: "Schrödinger", type: "PERSON", x: 180, y: 380, description: "Developed wave mechanics (1926); wave function ψ describes quantum state" }, { id: "planck", label: "Max Planck", type: "PERSON", x: 720, y: 380, description: "Introduced energy quanta E=hf (1900) to explain blackbody radiation — started QM" }, { id: "uncertainty", label: "Uncertainty Principle",type: "CONCEPT", x: 820, y: 260, description: "ΔxΔp ≥ ℏ/2 — position and momentum cannot both be precisely known simultaneously" }, { id: "wave_fn", label: "Wave Function ψ", type: "CONCEPT", x: 80, y: 260, description: "Mathematical description of quantum state; |ψ|² gives probability density" }, { id: "atom_model", label: "Bohr Atom Model", type: "CONCEPT", x: 80, y: 110, description: "Quantized electron energy levels; explained hydrogen emission spectrum (1913)" }, { id: "photoelectric",label:"Photoelectric Effect", type: "CONCEPT", x: 820, y: 110, description: "Light ejects electrons from metal — explained by Einstein (1905), uses Planck's quanta" }, ], edges: [ { source: "q", target: "qm", label: "FOUND_ENTITY" }, { source: "q", target: "bohr", label: "FOUND_ENTITY" }, { source: "planck", target: "qm", label: "FOUNDED_1900" }, { source: "bohr", target: "qm", label: "DEVELOPED" }, { source: "heisenberg", target: "qm", label: "DEVELOPED" }, { source: "schrodinger", target: "qm", label: "DEVELOPED" }, { source: "heisenberg", target: "uncertainty", label: "FORMULATED_1927" }, { source: "schrodinger", target: "wave_fn", label: "PROPOSED_1926" }, { source: "bohr", target: "atom_model", label: "PROPOSED_1913" }, { source: "planck", target: "photoelectric", label: "QUANTA_EXPLAIN" }, ], reasoning: [ "Entry: Quantum Mechanics identified; four physicist entities extracted", "Hop 1: FOUNDED/DEVELOPED edges — Planck, Bohr, Heisenberg, Schrödinger each contributed", "Hop 2: Specific contributions — Uncertainty Principle, Wave Function, Bohr Atom", "Convergence: All four paths meet at Quantum Mechanics — multi-founder answer confirmed", ], }, ]; // ── BFS hop reachability ─────────────────────────────────────────────────── function computeReachability( nodes: GraphNode[], edges: GraphEdge[], maxHops: number, ): Map { const queryNode = nodes.find(n => n.type === "QUERY"); if (!queryNode) return new Map(nodes.map(n => [n.id, 0])); const depths = new Map(); const queue: { id: string; depth: number }[] = [{ id: queryNode.id, depth: 0 }]; while (queue.length > 0) { const { id, depth } = queue.shift()!; if (depths.has(id)) continue; depths.set(id, depth); if (depth < maxHops) { for (const e of edges) { if (e.source === id && !depths.has(e.target)) queue.push({ id: e.target, depth: depth + 1 }); if (e.target === id && !depths.has(e.source)) queue.push({ id: e.source, depth: depth + 1 }); } } } return depths; } // ── Live query state ─────────────────────────────────────────────────────── interface LiveNode { id: string; label: string; x: number; y: number; hop: number } interface LiveEdge { source: string; target: string } function buildLiveGraph(entities: string[], query: string): { nodes: LiveNode[]; edges: LiveEdge[] } { const cx = 450, cy = 240; const nodes: LiveNode[] = [{ id: "q", label: query.slice(0, 32) + "…", x: cx, y: cy, hop: 0 }]; const edges: LiveEdge[] = []; const r = 170; entities.slice(0, 8).forEach((e, i) => { const angle = (2 * Math.PI * i) / Math.min(entities.length, 8) - Math.PI / 2; nodes.push({ id: `e${i}`, label: e.slice(0, 30), x: Math.round(cx + r * Math.cos(angle)), y: Math.round(cy + r * Math.sin(angle)), hop: 1, }); edges.push({ source: "q", target: `e${i}` }); }); return { nodes, edges }; } export function ExplorerContent() { const [scenarioIdx, setScenarioIdx] = useState(0); const [selectedNode, setSelectedNode] = useState(null); const [hops, setHops] = useState(3); // Live query state const [liveQuery, setLiveQuery] = useState(""); const [liveLoading, setLiveLoading] = useState(false); const [liveGraph, setLiveGraph] = useState<{ nodes: LiveNode[]; edges: LiveEdge[] } | null>(null); const [liveError, setLiveError] = useState(""); const scenario = SCENARIOS[scenarioIdx]; const { nodes, edges, reasoning } = scenario; // BFS hop filter — what actually changes when the slider moves const reachabilityMap = useMemo( () => computeReachability(nodes, edges, hops), [nodes, edges, hops], ); const visibleNodes = useMemo(() => nodes.filter(n => reachabilityMap.has(n.id)), [nodes, reachabilityMap]); const visibleEdges = useMemo( () => edges.filter(e => reachabilityMap.has(e.source) && reachabilityMap.has(e.target)), [edges, reachabilityMap], ); const nodeMap = useMemo(() => { const m: Record = {}; nodes.forEach(n => { m[n.id] = n; }); return m; }, [nodes]); const selectedInfo = selectedNode ? nodeMap[selectedNode] : null; const selectedDepth = selectedNode ? reachabilityMap.get(selectedNode) : undefined; const connectedEdges = selectedNode ? visibleEdges.filter(e => e.source === selectedNode || e.target === selectedNode) : []; const [liveAnswer, setLiveAnswer] = useState(null); const runLiveQuery = useCallback(async () => { if (!liveQuery.trim()) return; setLiveLoading(true); setLiveError(""); setLiveGraph(null); setLiveAnswer(null); try { const res = await fetch("/api/compare", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query: liveQuery, provider: "openai", topK: 8 }), }); const data = await res.json(); const entities: string[] = data.graphrag?.entities ?? []; const answer: string = data.graphrag?.answer ?? ""; if (entities.length === 0) { setLiveAnswer(answer || null); setLiveError( "No graph entities found for this query — the Wikipedia science corpus covers physics, biology, " + "chemistry, and astronomy. Try one of the example questions below." ); } else { setLiveGraph(buildLiveGraph(entities, liveQuery)); } } catch { setLiveError("Request failed — check that the dev server is running and OPENAI_API_KEY is set in web/.env."); } setLiveLoading(false); }, [liveQuery]); return (
{/* Scenario Selector */}
Scenario
{SCENARIOS.map((s, i) => ( ))}
“{scenario.query}”
{visibleNodes.length}/{nodes.length} nodes · {visibleEdges.length}/{edges.length} edges
{/* Graph SVG — 3 cols */}
{/* Dimmed nodes that are hidden by hop filter */} {nodes.filter(n => !reachabilityMap.has(n.id)).map(node => ( ))} {/* Edges */} {visibleEdges.map((edge, i) => { const s = nodeMap[edge.source]; const t = nodeMap[edge.target]; if (!s || !t) return null; const isConnected = selectedNode && (edge.source === selectedNode || edge.target === selectedNode); const dimmed = selectedNode && !isConnected; const mx = (s.x + t.x) / 2; const my = (s.y + t.y) / 2 - 10; return ( {edge.label} ); })} {/* Nodes */} {visibleNodes.map(node => { const color = TYPE_COLORS[node.type] || "#AED6F1"; const isSelected = selectedNode === node.id; const isConnected = connectedEdges.some(e => e.source === node.id || e.target === node.id); const dimmed = selectedNode && !isSelected && !isConnected; const depth = reachabilityMap.get(node.id) ?? 0; const r = node.type === "QUERY" ? 26 : isSelected ? 24 : 20; return ( setSelectedNode(isSelected ? null : node.id)} opacity={dimmed ? 0.18 : 1} > {/* Glow rings for selected */} {isSelected && ( <> )} {/* Hop depth badge (small dot in top-right of node) */} {depth > 0 && ( {depth} )} {node.type === "QUERY" && ( ? )} {node.label} ); })}
{/* Sidebar */}
{/* Node Details */}
{selectedInfo ? "Node Details" : "Select a Node"}
{selectedInfo ? (
{selectedInfo.label}
{selectedInfo.type} {selectedDepth !== undefined && ( Hop {selectedDepth} from query )}
{selectedInfo.description && (

{selectedInfo.description}

)}
{connectedEdges.length} visible connection{connectedEdges.length !== 1 ? "s" : ""}
{connectedEdges.map((e, i) => { const otherId = e.source === selectedInfo.id ? e.target : e.source; const otherNode = nodeMap[otherId]; const dir = e.source === selectedInfo.id ? "→" : "←"; return (
setSelectedNode(otherId)}>
{dir} {e.label} {otherNode?.label}
); })}
) : (

Click any visible node to inspect its entity type, hop distance from the query, and connections. Use the hops slider to see the graph expand step by step.

)}
{/* Graph Stats */}
Graph Statistics
{[ { label: "Visible Nodes", value: `${visibleNodes.length} / ${nodes.length}`, color: "#FF6B00" }, { label: "Visible Edges", value: `${visibleEdges.length} / ${edges.length}`, color: "#0072CE" }, { label: "Max Hops", value: hops, color: "#5db8a6" }, { label: "Avg Degree", value: visibleNodes.length > 0 ? (visibleEdges.length * 2 / visibleNodes.length).toFixed(1) : "0", color: "#cc785c" }, { label: "Entity Types", value: new Set(visibleNodes.map(n => n.type)).size, color: "#002B49" }, ].map((s, i) => (
{s.label} {s.value}
))}
{/* Legend */}
Entity Types
{Object.entries(TYPE_COLORS).map(([type, color]) => (
{type}
))}

Small number on each node = hop distance from query node.

{/* Reasoning Steps */}
🧠 Graph Reasoning Path
{reasoning.map((step, i) => { const active = i < hops; return (
Step {i + 1}

{step}

); })}

Steps highlight based on the current hop depth. Drag the slider above to walk through the reasoning.

{/* Live Query Section */}
🔴 Live Entity Query

Ask a science question — GraphRAG retrieves entities from TigerGraph and renders them as a live graph. Corpus covers physics, biology, chemistry, and astronomy.

{/* Example chips */}
{[ "How do black holes form?", "What is CRISPR and how does it edit DNA?", "How does photosynthesis produce oxygen?", "What causes quantum entanglement?", "How does the immune system fight viruses?", ].map(q => ( ))}
setLiveQuery(e.target.value)} onKeyDown={e => { if (e.key === "Enter") runLiveQuery(); }} />
{liveError && (
{liveError}
)} {liveAnswer && !liveGraph && (
GraphRAG Answer

{liveAnswer}

)} {liveGraph && (
{liveGraph.nodes.length - 1} entities retrieved from TigerGraph — star topology (query → entities, hop 1)
{liveGraph.edges.map((e, i) => { const s = liveGraph.nodes.find(n => n.id === e.source); const t = liveGraph.nodes.find(n => n.id === e.target); if (!s || !t) return null; return ( ); })} {liveGraph.nodes.map((node, i) => { const isQuery = node.id === "q"; const color = isQuery ? "#FF6B00" : "#AED6F1"; const r = isQuery ? 26 : 20; return ( {isQuery && ( ? )}
{node.label}
); })}
)}
); }