🎨 Ultra premium UI: 6 separate pages with animations, bento grid, premium design
Browse files- web/src/app/architecture/page.tsx +13 -0
- web/src/app/benchmarks/page.tsx +27 -0
- web/src/app/docs/page.tsx +13 -0
- web/src/app/explorer/page.tsx +27 -0
- web/src/app/playground/page.tsx +27 -0
- web/src/components/architecture/ArchitectureContent.tsx +266 -0
- web/src/components/benchmarks/BenchmarkContent.tsx +347 -0
- web/src/components/docs/DocsContent.tsx +379 -0
- web/src/components/explorer/ExplorerContent.tsx +357 -0
- web/src/components/home/BentoShowcase.tsx +85 -0
- web/src/components/home/CTASection.tsx +64 -0
- web/src/components/home/FeaturesSection.tsx +101 -0
- web/src/components/home/HowItWorks.tsx +96 -0
- web/src/components/home/TestimonialsSection.tsx +69 -0
- web/src/components/playground/PlaygroundContent.tsx +318 -0
web/src/app/architecture/page.tsx
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Navbar } from "@/components/Navbar";
|
| 2 |
+
import { Footer } from "@/components/Footer";
|
| 3 |
+
import { ArchitectureContent } from "@/components/architecture/ArchitectureContent";
|
| 4 |
+
|
| 5 |
+
export default function ArchitecturePage() {
|
| 6 |
+
return (
|
| 7 |
+
<main>
|
| 8 |
+
<Navbar />
|
| 9 |
+
<ArchitectureContent />
|
| 10 |
+
<Footer />
|
| 11 |
+
</main>
|
| 12 |
+
);
|
| 13 |
+
}
|
web/src/app/benchmarks/page.tsx
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Navbar } from "@/components/Navbar";
|
| 2 |
+
import { Footer } from "@/components/Footer";
|
| 3 |
+
import { BenchmarkContent } from "@/components/benchmarks/BenchmarkContent";
|
| 4 |
+
|
| 5 |
+
export default function BenchmarksPage() {
|
| 6 |
+
return (
|
| 7 |
+
<main>
|
| 8 |
+
<Navbar />
|
| 9 |
+
<div className="page-header">
|
| 10 |
+
<div className="container">
|
| 11 |
+
<div className="badge-blue mb-4" style={{ fontSize: "0.75rem" }}>📊 Performance</div>
|
| 12 |
+
<h1 className="display-xl mb-3">Benchmarks</h1>
|
| 13 |
+
<p className="body-lg mx-auto" style={{ maxWidth: "560px", color: "var(--color-muted)" }}>
|
| 14 |
+
Run batch evaluations on HotpotQA questions. Compare F1 score, exact match,
|
| 15 |
+
token usage, and cost across both pipelines.
|
| 16 |
+
</p>
|
| 17 |
+
</div>
|
| 18 |
+
</div>
|
| 19 |
+
<section style={{ background: "var(--color-surface-soft)", padding: "0 0 96px" }}>
|
| 20 |
+
<div className="container">
|
| 21 |
+
<BenchmarkContent />
|
| 22 |
+
</div>
|
| 23 |
+
</section>
|
| 24 |
+
<Footer />
|
| 25 |
+
</main>
|
| 26 |
+
);
|
| 27 |
+
}
|
web/src/app/docs/page.tsx
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Navbar } from "@/components/Navbar";
|
| 2 |
+
import { Footer } from "@/components/Footer";
|
| 3 |
+
import { DocsContent } from "@/components/docs/DocsContent";
|
| 4 |
+
|
| 5 |
+
export default function DocsPage() {
|
| 6 |
+
return (
|
| 7 |
+
<main>
|
| 8 |
+
<Navbar />
|
| 9 |
+
<DocsContent />
|
| 10 |
+
<Footer />
|
| 11 |
+
</main>
|
| 12 |
+
);
|
| 13 |
+
}
|
web/src/app/explorer/page.tsx
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Navbar } from "@/components/Navbar";
|
| 2 |
+
import { Footer } from "@/components/Footer";
|
| 3 |
+
import { ExplorerContent } from "@/components/explorer/ExplorerContent";
|
| 4 |
+
|
| 5 |
+
export default function ExplorerPage() {
|
| 6 |
+
return (
|
| 7 |
+
<main>
|
| 8 |
+
<Navbar />
|
| 9 |
+
<div className="page-header">
|
| 10 |
+
<div className="container">
|
| 11 |
+
<div className="badge" style={{ background: "#e8f8f5", color: "#5db8a6", fontSize: "0.75rem" }}>🕸️ Knowledge Graph</div>
|
| 12 |
+
<h1 className="display-xl mb-3 mt-4">Graph Explorer</h1>
|
| 13 |
+
<p className="body-lg mx-auto" style={{ maxWidth: "560px", color: "var(--color-muted)" }}>
|
| 14 |
+
Visualize how GraphRAG traverses the knowledge graph to find answers.
|
| 15 |
+
Click nodes to inspect entities and trace reasoning paths.
|
| 16 |
+
</p>
|
| 17 |
+
</div>
|
| 18 |
+
</div>
|
| 19 |
+
<section style={{ background: "var(--color-surface-soft)", padding: "0 0 96px" }}>
|
| 20 |
+
<div className="container-wide">
|
| 21 |
+
<ExplorerContent />
|
| 22 |
+
</div>
|
| 23 |
+
</section>
|
| 24 |
+
<Footer />
|
| 25 |
+
</main>
|
| 26 |
+
);
|
| 27 |
+
}
|
web/src/app/playground/page.tsx
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Navbar } from "@/components/Navbar";
|
| 2 |
+
import { Footer } from "@/components/Footer";
|
| 3 |
+
import { PlaygroundContent } from "@/components/playground/PlaygroundContent";
|
| 4 |
+
|
| 5 |
+
export default function PlaygroundPage() {
|
| 6 |
+
return (
|
| 7 |
+
<main>
|
| 8 |
+
<Navbar />
|
| 9 |
+
<div className="page-header">
|
| 10 |
+
<div className="container">
|
| 11 |
+
<div className="badge-glow mb-4" style={{ fontSize: "0.75rem" }}>⚡ Live Demo</div>
|
| 12 |
+
<h1 className="display-xl mb-3">Playground</h1>
|
| 13 |
+
<p className="body-lg mx-auto" style={{ maxWidth: "560px", color: "var(--color-muted)" }}>
|
| 14 |
+
Ask any question and watch Baseline RAG race against GraphRAG in real-time.
|
| 15 |
+
Compare answers, tokens, latency, and cost side-by-side.
|
| 16 |
+
</p>
|
| 17 |
+
</div>
|
| 18 |
+
</div>
|
| 19 |
+
<section style={{ background: "var(--color-surface-soft)", padding: "0 0 96px" }}>
|
| 20 |
+
<div className="container">
|
| 21 |
+
<PlaygroundContent />
|
| 22 |
+
</div>
|
| 23 |
+
</section>
|
| 24 |
+
<Footer />
|
| 25 |
+
</main>
|
| 26 |
+
);
|
| 27 |
+
}
|
web/src/components/architecture/ArchitectureContent.tsx
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
const LAYERS = [
|
| 4 |
+
{
|
| 5 |
+
number: "01",
|
| 6 |
+
name: "Graph Layer",
|
| 7 |
+
tech: "TigerGraph Cloud",
|
| 8 |
+
color: "#e8a55a",
|
| 9 |
+
icon: "🔶",
|
| 10 |
+
description: "Foundation of the system. TigerGraph stores entities, relationships, and their properties as a native graph. GSQL queries enable multi-hop traversal that would be prohibitively expensive with traditional databases.",
|
| 11 |
+
capabilities: [
|
| 12 |
+
"Entity storage with typed vertices (PERSON, LOCATION, WORK, etc.)",
|
| 13 |
+
"Relationship edges with properties (BORN_IN, DIRECTED, etc.)",
|
| 14 |
+
"GSQL queries for 1-hop, 2-hop, and multi-hop traversal",
|
| 15 |
+
"Schema-bounded extraction — only valid vertex types accepted",
|
| 16 |
+
"Real-time graph updates via ingestion pipeline",
|
| 17 |
+
],
|
| 18 |
+
code: `# GSQL Multi-Hop Query
|
| 19 |
+
CREATE QUERY find_connections(VERTEX<Entity> start, INT hops) {
|
| 20 |
+
Start = {start};
|
| 21 |
+
FOREACH i IN RANGE[1, hops] DO
|
| 22 |
+
Start = SELECT t
|
| 23 |
+
FROM Start:s -(HAS_RELATION:e)-> Entity:t
|
| 24 |
+
ACCUM @@paths += (s, e.relation, t);
|
| 25 |
+
END;
|
| 26 |
+
PRINT @@paths;
|
| 27 |
+
}`,
|
| 28 |
+
},
|
| 29 |
+
{
|
| 30 |
+
number: "02",
|
| 31 |
+
name: "Orchestration Layer",
|
| 32 |
+
tech: "Dual Pipeline Router",
|
| 33 |
+
color: "#0072CE",
|
| 34 |
+
icon: "🔀",
|
| 35 |
+
description: "The brain of the system. Analyzes incoming queries, classifies their complexity, and routes them through the appropriate pipeline — Baseline RAG for simple queries, GraphRAG for complex multi-hop questions.",
|
| 36 |
+
capabilities: [
|
| 37 |
+
"Adaptive Query Router — complexity scoring (0.0–1.0)",
|
| 38 |
+
"Query type classification (bridge, comparison, factoid)",
|
| 39 |
+
"Dual-Level Keyword extraction (high-level concepts + low-level entities)",
|
| 40 |
+
"Pipeline A: Query → Vector Search → LLM (fast, cheap)",
|
| 41 |
+
"Pipeline B: Query → Entity Extraction → Graph Traversal → LLM (precise)",
|
| 42 |
+
],
|
| 43 |
+
code: `# Adaptive Query Router
|
| 44 |
+
class AdaptiveRouter:
|
| 45 |
+
def classify(self, query: str) -> RouteDecision:
|
| 46 |
+
complexity = self.score_complexity(query)
|
| 47 |
+
query_type = self.detect_type(query) # bridge/comparison/factoid
|
| 48 |
+
|
| 49 |
+
if complexity > 0.6 or query_type == "bridge":
|
| 50 |
+
return Route.GRAPHRAG
|
| 51 |
+
return Route.BASELINE`,
|
| 52 |
+
},
|
| 53 |
+
{
|
| 54 |
+
number: "03",
|
| 55 |
+
name: "LLM Layer",
|
| 56 |
+
tech: "12 Providers via Universal API",
|
| 57 |
+
color: "#cc785c",
|
| 58 |
+
icon: "🤖",
|
| 59 |
+
description: "Universal LLM abstraction that supports 12 providers through a single API. Swap between Claude, GPT-4, Gemini, Llama, and more with one parameter change — no code modifications needed.",
|
| 60 |
+
capabilities: [
|
| 61 |
+
"Anthropic Claude (Sonnet 4, Haiku 4)",
|
| 62 |
+
"OpenAI (GPT-4o, GPT-4o-mini)",
|
| 63 |
+
"Google Gemini (2.0 Flash, Pro)",
|
| 64 |
+
"Meta Llama via Groq / Together / HuggingFace",
|
| 65 |
+
"Mistral, DeepSeek, Cohere, xAI Grok, OpenRouter",
|
| 66 |
+
"Local: Ollama for fully offline inference",
|
| 67 |
+
],
|
| 68 |
+
code: `# Universal LLM — one interface, 12 providers
|
| 69 |
+
llm = UniversalLLM(provider="anthropic", model="claude-sonnet-4")
|
| 70 |
+
response = llm.generate(
|
| 71 |
+
context=graph_evidence,
|
| 72 |
+
query=user_question,
|
| 73 |
+
max_tokens=500
|
| 74 |
+
)
|
| 75 |
+
# Switch provider with one line:
|
| 76 |
+
llm = UniversalLLM(provider="groq", model="llama-3.3-70b")`,
|
| 77 |
+
},
|
| 78 |
+
{
|
| 79 |
+
number: "04",
|
| 80 |
+
name: "Evaluation Layer",
|
| 81 |
+
tech: "RAGAS + F1/EM + Cost Tracking",
|
| 82 |
+
color: "#5db8a6",
|
| 83 |
+
icon: "📊",
|
| 84 |
+
description: "Automated evaluation that measures every query. Computes F1 score, Exact Match, RAGAS metrics, token usage, latency, and USD cost for both pipelines. Powers the benchmark dashboard and cost projections.",
|
| 85 |
+
capabilities: [
|
| 86 |
+
"F1 Score — token-level overlap with ground truth",
|
| 87 |
+
"Exact Match — binary correctness metric",
|
| 88 |
+
"RAGAS integration — faithfulness, relevancy, context metrics",
|
| 89 |
+
"Token counting — input/output per provider",
|
| 90 |
+
"Cost tracking — USD per query based on provider pricing",
|
| 91 |
+
"Latency measurement — end-to-end milliseconds",
|
| 92 |
+
],
|
| 93 |
+
code: `# Evaluation Layer
|
| 94 |
+
evaluator = RAGASEvaluator()
|
| 95 |
+
metrics = evaluator.evaluate(
|
| 96 |
+
query=question,
|
| 97 |
+
answer=llm_response,
|
| 98 |
+
ground_truth=reference_answer,
|
| 99 |
+
context=retrieved_context
|
| 100 |
+
)
|
| 101 |
+
# Returns: { f1: 0.89, em: 1.0, tokens: 2400,
|
| 102 |
+
# cost_usd: 0.0096, latency_ms: 1800 }`,
|
| 103 |
+
},
|
| 104 |
+
];
|
| 105 |
+
|
| 106 |
+
export function ArchitectureContent() {
|
| 107 |
+
return (
|
| 108 |
+
<div>
|
| 109 |
+
{/* Hero */}
|
| 110 |
+
<section style={{
|
| 111 |
+
background: "linear-gradient(135deg, #002B49 0%, #003D6B 50%, #002B49 100%)",
|
| 112 |
+
padding: "96px 0 80px",
|
| 113 |
+
position: "relative",
|
| 114 |
+
overflow: "hidden",
|
| 115 |
+
}}>
|
| 116 |
+
<div style={{
|
| 117 |
+
position: "absolute", top: "-50%", right: "-20%",
|
| 118 |
+
width: "800px", height: "800px",
|
| 119 |
+
borderRadius: "50%",
|
| 120 |
+
background: "radial-gradient(circle, rgba(255,107,0,0.08) 0%, transparent 70%)",
|
| 121 |
+
}} />
|
| 122 |
+
<div className="container" style={{ position: "relative" }}>
|
| 123 |
+
<div className="badge mb-4" style={{ background: "rgba(255,107,0,0.15)", color: "#FF6B00", fontSize: "0.75rem" }}>
|
| 124 |
+
🏗️ System Design
|
| 125 |
+
</div>
|
| 126 |
+
<h1 style={{
|
| 127 |
+
fontFamily: "var(--font-serif)",
|
| 128 |
+
fontSize: "clamp(2.5rem, 5vw, 4rem)",
|
| 129 |
+
fontWeight: 400,
|
| 130 |
+
color: "white",
|
| 131 |
+
letterSpacing: "-1.5px",
|
| 132 |
+
marginBottom: "16px",
|
| 133 |
+
}}>
|
| 134 |
+
Architecture
|
| 135 |
+
</h1>
|
| 136 |
+
<p style={{ fontSize: "1.125rem", color: "rgba(255,255,255,0.6)", maxWidth: "560px", lineHeight: 1.6 }}>
|
| 137 |
+
A 4-layer AI Factory model inspired by the GraphRAG and LightRAG papers,
|
| 138 |
+
built on TigerGraph for graph storage and traversal.
|
| 139 |
+
</p>
|
| 140 |
+
|
| 141 |
+
{/* Layer Overview Diagram */}
|
| 142 |
+
<div className="mt-12 grid grid-cols-4 gap-3">
|
| 143 |
+
{LAYERS.map((layer, i) => (
|
| 144 |
+
<div key={i} style={{
|
| 145 |
+
background: "rgba(255,255,255,0.06)",
|
| 146 |
+
border: `1px solid ${layer.color}30`,
|
| 147 |
+
borderRadius: "12px",
|
| 148 |
+
padding: "20px",
|
| 149 |
+
textAlign: "center",
|
| 150 |
+
}}>
|
| 151 |
+
<div style={{ fontSize: "1.5rem", marginBottom: "8px" }}>{layer.icon}</div>
|
| 152 |
+
<div style={{ fontFamily: "var(--font-mono)", fontSize: "0.6875rem", color: layer.color, marginBottom: "4px" }}>
|
| 153 |
+
Layer {layer.number}
|
| 154 |
+
</div>
|
| 155 |
+
<div style={{ color: "white", fontWeight: 500, fontSize: "0.875rem" }}>
|
| 156 |
+
{layer.name}
|
| 157 |
+
</div>
|
| 158 |
+
<div style={{ color: "rgba(255,255,255,0.5)", fontSize: "0.75rem", marginTop: "4px" }}>
|
| 159 |
+
{layer.tech}
|
| 160 |
+
</div>
|
| 161 |
+
</div>
|
| 162 |
+
))}
|
| 163 |
+
</div>
|
| 164 |
+
|
| 165 |
+
{/* Flow arrows */}
|
| 166 |
+
<div className="flex justify-center items-center mt-6 gap-2">
|
| 167 |
+
<span style={{ color: "rgba(255,255,255,0.3)", fontFamily: "var(--font-mono)", fontSize: "0.75rem" }}>
|
| 168 |
+
Query → Graph → Orchestration → LLM → Evaluation → Answer
|
| 169 |
+
</span>
|
| 170 |
+
</div>
|
| 171 |
+
</div>
|
| 172 |
+
</section>
|
| 173 |
+
|
| 174 |
+
{/* Layer Details */}
|
| 175 |
+
<section className="section">
|
| 176 |
+
<div className="container">
|
| 177 |
+
{LAYERS.map((layer, i) => (
|
| 178 |
+
<div key={i} className="mb-16" style={{ paddingBottom: i < LAYERS.length - 1 ? "64px" : "0", borderBottom: i < LAYERS.length - 1 ? "1px solid var(--color-hairline-soft)" : "none" }}>
|
| 179 |
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-start">
|
| 180 |
+
{/* Info */}
|
| 181 |
+
<div className={i % 2 === 0 ? "" : "lg:order-2"}>
|
| 182 |
+
<div className="flex items-center gap-3 mb-4">
|
| 183 |
+
<span style={{
|
| 184 |
+
fontFamily: "var(--font-mono)", fontSize: "0.8125rem",
|
| 185 |
+
color: layer.color, fontWeight: 700,
|
| 186 |
+
}}>
|
| 187 |
+
Layer {layer.number}
|
| 188 |
+
</span>
|
| 189 |
+
<div className="divider" style={{ background: layer.color }} />
|
| 190 |
+
</div>
|
| 191 |
+
<h2 className="display-md mb-2">{layer.name}</h2>
|
| 192 |
+
<div className="badge mb-4" style={{
|
| 193 |
+
background: `${layer.color}12`, color: layer.color, fontSize: "0.75rem",
|
| 194 |
+
}}>
|
| 195 |
+
{layer.tech}
|
| 196 |
+
</div>
|
| 197 |
+
<p className="body-lg mb-6" style={{ color: "var(--color-muted)", lineHeight: 1.7 }}>
|
| 198 |
+
{layer.description}
|
| 199 |
+
</p>
|
| 200 |
+
|
| 201 |
+
{/* Capabilities */}
|
| 202 |
+
<div className="flex flex-col gap-2.5">
|
| 203 |
+
{layer.capabilities.map((cap, j) => (
|
| 204 |
+
<div key={j} className="flex items-start gap-3">
|
| 205 |
+
<div style={{
|
| 206 |
+
width: "6px", height: "6px", borderRadius: "50%",
|
| 207 |
+
background: layer.color, marginTop: "8px", flexShrink: 0,
|
| 208 |
+
}} />
|
| 209 |
+
<span className="body-sm" style={{ color: "var(--color-body)" }}>{cap}</span>
|
| 210 |
+
</div>
|
| 211 |
+
))}
|
| 212 |
+
</div>
|
| 213 |
+
</div>
|
| 214 |
+
|
| 215 |
+
{/* Code */}
|
| 216 |
+
<div className={i % 2 === 0 ? "" : "lg:order-1"}>
|
| 217 |
+
<div className="code-window">
|
| 218 |
+
<div className="code-window-header">
|
| 219 |
+
<div className="code-window-dot code-window-dot-red" />
|
| 220 |
+
<div className="code-window-dot code-window-dot-yellow" />
|
| 221 |
+
<div className="code-window-dot code-window-dot-green" />
|
| 222 |
+
<span className="body-sm" style={{ color: "#a09d96", marginLeft: "12px" }}>
|
| 223 |
+
{layer.name.toLowerCase().replace(/\s/g, "_")}.py
|
| 224 |
+
</span>
|
| 225 |
+
</div>
|
| 226 |
+
<pre className="code-window-body" style={{ fontSize: "0.8125rem", lineHeight: 1.8, whiteSpace: "pre-wrap" }}>
|
| 227 |
+
{layer.code}
|
| 228 |
+
</pre>
|
| 229 |
+
</div>
|
| 230 |
+
</div>
|
| 231 |
+
</div>
|
| 232 |
+
</div>
|
| 233 |
+
))}
|
| 234 |
+
</div>
|
| 235 |
+
</section>
|
| 236 |
+
|
| 237 |
+
{/* Tech Stack */}
|
| 238 |
+
<section className="section" style={{ background: "var(--color-surface-soft)" }}>
|
| 239 |
+
<div className="container">
|
| 240 |
+
<div className="text-center mb-12">
|
| 241 |
+
<div className="caption-uppercase mb-3" style={{ color: "var(--color-tiger-orange)" }}>Tech Stack</div>
|
| 242 |
+
<h2 className="display-lg">Built with modern tools</h2>
|
| 243 |
+
</div>
|
| 244 |
+
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
| 245 |
+
{[
|
| 246 |
+
{ name: "TigerGraph", role: "Graph Database", icon: "🔶" },
|
| 247 |
+
{ name: "Claude", role: "Primary LLM", icon: "🤖" },
|
| 248 |
+
{ name: "Python", role: "Backend", icon: "🐍" },
|
| 249 |
+
{ name: "Next.js", role: "Frontend", icon: "⚡" },
|
| 250 |
+
{ name: "Recharts", role: "Visualizations", icon: "📊" },
|
| 251 |
+
{ name: "Docker", role: "Deployment", icon: "🐳" },
|
| 252 |
+
{ name: "RAGAS", role: "Evaluation", icon: "📋" },
|
| 253 |
+
{ name: "HotpotQA", role: "Benchmark Data", icon: "📚" },
|
| 254 |
+
].map((tech, i) => (
|
| 255 |
+
<div key={i} className="card card-hover text-center" style={{ padding: "28px 16px" }}>
|
| 256 |
+
<div style={{ fontSize: "2rem", marginBottom: "8px" }}>{tech.icon}</div>
|
| 257 |
+
<div className="title-sm">{tech.name}</div>
|
| 258 |
+
<div className="caption" style={{ marginTop: "2px" }}>{tech.role}</div>
|
| 259 |
+
</div>
|
| 260 |
+
))}
|
| 261 |
+
</div>
|
| 262 |
+
</div>
|
| 263 |
+
</section>
|
| 264 |
+
</div>
|
| 265 |
+
);
|
| 266 |
+
}
|
web/src/components/benchmarks/BenchmarkContent.tsx
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { useState } from "react";
|
| 4 |
+
import {
|
| 5 |
+
RadarChart, Radar, PolarGrid, PolarAngleAxis,
|
| 6 |
+
ResponsiveContainer, Tooltip, Legend,
|
| 7 |
+
BarChart, Bar, XAxis, YAxis, CartesianGrid,
|
| 8 |
+
AreaChart, Area,
|
| 9 |
+
} from "recharts";
|
| 10 |
+
|
| 11 |
+
interface AggregateData {
|
| 12 |
+
numSamples: number;
|
| 13 |
+
baseline: { avgF1: number; avgEM: number; avgTokens: number; avgCost: number; avgLatency: number };
|
| 14 |
+
graphrag: { avgF1: number; avgEM: number; avgTokens: number; avgCost: number; avgLatency: number };
|
| 15 |
+
graphragF1WinRate: number;
|
| 16 |
+
byType: {
|
| 17 |
+
bridge?: { count: number; baselineF1: number; graphragF1: number } | null;
|
| 18 |
+
comparison?: { count: number; baselineF1: number; graphragF1: number } | null;
|
| 19 |
+
};
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
const INITIAL: AggregateData = {
|
| 23 |
+
numSamples: 0,
|
| 24 |
+
baseline: { avgF1: 0, avgEM: 0, avgTokens: 0, avgCost: 0, avgLatency: 0 },
|
| 25 |
+
graphrag: { avgF1: 0, avgEM: 0, avgTokens: 0, avgCost: 0, avgLatency: 0 },
|
| 26 |
+
graphragF1WinRate: 0,
|
| 27 |
+
byType: {},
|
| 28 |
+
};
|
| 29 |
+
|
| 30 |
+
// Pre-computed demo results for showcase
|
| 31 |
+
const DEMO_DATA: AggregateData = {
|
| 32 |
+
numSamples: 10,
|
| 33 |
+
baseline: { avgF1: 0.6234, avgEM: 0.4000, avgTokens: 950, avgCost: 0.003800, avgLatency: 1200 },
|
| 34 |
+
graphrag: { avgF1: 0.7567, avgEM: 0.5000, avgTokens: 2400, avgCost: 0.009600, avgLatency: 1800 },
|
| 35 |
+
graphragF1WinRate: 0.70,
|
| 36 |
+
byType: {
|
| 37 |
+
bridge: { count: 5, baselineF1: 0.5800, graphragF1: 0.7900 },
|
| 38 |
+
comparison: { count: 5, baselineF1: 0.6700, graphragF1: 0.7200 },
|
| 39 |
+
},
|
| 40 |
+
};
|
| 41 |
+
|
| 42 |
+
export function BenchmarkContent() {
|
| 43 |
+
const [running, setRunning] = useState(false);
|
| 44 |
+
const [samples, setSamples] = useState(10);
|
| 45 |
+
const [data, setData] = useState<AggregateData>(DEMO_DATA);
|
| 46 |
+
const [report, setReport] = useState("");
|
| 47 |
+
const [demoMode, setDemoMode] = useState(true);
|
| 48 |
+
const [hasResults, setHasResults] = useState(true);
|
| 49 |
+
|
| 50 |
+
const runBenchmark = async () => {
|
| 51 |
+
setRunning(true);
|
| 52 |
+
setReport("Running benchmark...");
|
| 53 |
+
try {
|
| 54 |
+
const res = await fetch("/api/benchmark", {
|
| 55 |
+
method: "POST",
|
| 56 |
+
headers: { "Content-Type": "application/json" },
|
| 57 |
+
body: JSON.stringify({ numSamples: samples }),
|
| 58 |
+
});
|
| 59 |
+
const result = await res.json();
|
| 60 |
+
setData(result.aggregate);
|
| 61 |
+
setDemoMode(result.demoMode ?? false);
|
| 62 |
+
setHasResults(true);
|
| 63 |
+
|
| 64 |
+
const a = result.aggregate;
|
| 65 |
+
const lines = [
|
| 66 |
+
`BENCHMARK RESULTS (${a.numSamples} samples, ${result.provider}/${result.model})`,
|
| 67 |
+
`${result.demoMode ? "⚠️ DEMO MODE" : "✅ LIVE RESULTS"}`,
|
| 68 |
+
"",
|
| 69 |
+
`Metric Baseline GraphRAG Winner`,
|
| 70 |
+
`${"─".repeat(60)}`,
|
| 71 |
+
`Avg F1 ${a.baseline.avgF1.toFixed(4)} ${a.graphrag.avgF1.toFixed(4)} ${a.graphrag.avgF1 > a.baseline.avgF1 ? "GraphRAG" : "Baseline"}`,
|
| 72 |
+
`Avg EM ${a.baseline.avgEM.toFixed(4)} ${a.graphrag.avgEM.toFixed(4)} ${a.graphrag.avgEM > a.baseline.avgEM ? "GraphRAG" : "Baseline"}`,
|
| 73 |
+
`Avg Tokens ${a.baseline.avgTokens} ${a.graphrag.avgTokens}`,
|
| 74 |
+
`GraphRAG F1 Win Rate: ${(a.graphragF1WinRate * 100).toFixed(0)}%`,
|
| 75 |
+
];
|
| 76 |
+
setReport(lines.join("\n"));
|
| 77 |
+
} catch (err) {
|
| 78 |
+
setReport(`Error: ${err}`);
|
| 79 |
+
}
|
| 80 |
+
setRunning(false);
|
| 81 |
+
};
|
| 82 |
+
|
| 83 |
+
const radarData = hasResults ? [
|
| 84 |
+
{ metric: "F1 Score", Baseline: +(data.baseline.avgF1 * 100).toFixed(0), GraphRAG: +(data.graphrag.avgF1 * 100).toFixed(0) },
|
| 85 |
+
{ metric: "Exact Match", Baseline: +(data.baseline.avgEM * 100).toFixed(0), GraphRAG: +(data.graphrag.avgEM * 100).toFixed(0) },
|
| 86 |
+
{ metric: "Speed", Baseline: 85, GraphRAG: Math.max(10, 100 - Math.round(data.graphrag.avgLatency / Math.max(data.baseline.avgLatency, 1) * 30)) },
|
| 87 |
+
{ metric: "Cost Eff.", Baseline: 85, GraphRAG: Math.max(10, 100 - Math.round(data.graphrag.avgCost / Math.max(data.baseline.avgCost, 0.000001) * 20)) },
|
| 88 |
+
{ metric: "Win Rate", Baseline: +((1 - data.graphragF1WinRate) * 100).toFixed(0), GraphRAG: +(data.graphragF1WinRate * 100).toFixed(0) },
|
| 89 |
+
] : [];
|
| 90 |
+
|
| 91 |
+
const typeData = [];
|
| 92 |
+
if (data.byType.bridge) typeData.push({ name: "Bridge", Baseline: +(data.byType.bridge.baselineF1 * 100).toFixed(1), GraphRAG: +(data.byType.bridge.graphragF1 * 100).toFixed(1) });
|
| 93 |
+
if (data.byType.comparison) typeData.push({ name: "Comparison", Baseline: +(data.byType.comparison.baselineF1 * 100).toFixed(1), GraphRAG: +(data.byType.comparison.graphragF1 * 100).toFixed(1) });
|
| 94 |
+
|
| 95 |
+
// Token efficiency data
|
| 96 |
+
const tokenData = [
|
| 97 |
+
{ name: "Input Tokens", Baseline: 800, GraphRAG: 2200 },
|
| 98 |
+
{ name: "Output Tokens", Baseline: 150, GraphRAG: 200 },
|
| 99 |
+
{ name: "Total", Baseline: data.baseline.avgTokens, GraphRAG: data.graphrag.avgTokens },
|
| 100 |
+
];
|
| 101 |
+
|
| 102 |
+
return (
|
| 103 |
+
<div>
|
| 104 |
+
{/* Run Controls */}
|
| 105 |
+
<div className="card mb-8 animate-fade-in-up">
|
| 106 |
+
<div className="flex flex-wrap items-end gap-6">
|
| 107 |
+
<div className="flex-1 min-w-[200px]">
|
| 108 |
+
<div className="display-sm mb-2">Run Benchmark</div>
|
| 109 |
+
<p className="body-sm" style={{ color: "var(--color-muted)" }}>
|
| 110 |
+
Evaluate both pipelines on HotpotQA multi-hop questions
|
| 111 |
+
</p>
|
| 112 |
+
</div>
|
| 113 |
+
<div className="flex items-center gap-6">
|
| 114 |
+
<div>
|
| 115 |
+
<label className="caption block mb-1">Samples</label>
|
| 116 |
+
<div className="flex items-center gap-3">
|
| 117 |
+
<input type="range" min={5} max={10} step={1} value={samples}
|
| 118 |
+
onChange={e => setSamples(+e.target.value)}
|
| 119 |
+
className="w-28 accent-[#FF6B00]" />
|
| 120 |
+
<span className="metric-value-sm" style={{ color: "var(--color-tiger-orange)", width: "2ch" }}>
|
| 121 |
+
{samples}
|
| 122 |
+
</span>
|
| 123 |
+
</div>
|
| 124 |
+
</div>
|
| 125 |
+
<button className="btn btn-primary btn-lg" onClick={runBenchmark} disabled={running}>
|
| 126 |
+
{running ? (
|
| 127 |
+
<span className="flex items-center gap-2">
|
| 128 |
+
<span className="animate-spin inline-block w-5 h-5 border-2 border-white border-t-transparent rounded-full" />
|
| 129 |
+
Running…
|
| 130 |
+
</span>
|
| 131 |
+
) : "🏃 Run Benchmark"}
|
| 132 |
+
</button>
|
| 133 |
+
</div>
|
| 134 |
+
</div>
|
| 135 |
+
{demoMode && hasResults && (
|
| 136 |
+
<div className="mt-4 pt-4" style={{ borderTop: "1px solid var(--color-hairline-soft)" }}>
|
| 137 |
+
<div className="flex items-center gap-2">
|
| 138 |
+
<span className="badge-outline" style={{ fontSize: "0.6875rem" }}>📊 Pre-computed Demo Results</span>
|
| 139 |
+
<span className="body-sm" style={{ color: "var(--color-muted)" }}>
|
| 140 |
+
Set an API key for live benchmark data
|
| 141 |
+
</span>
|
| 142 |
+
</div>
|
| 143 |
+
</div>
|
| 144 |
+
)}
|
| 145 |
+
</div>
|
| 146 |
+
|
| 147 |
+
{hasResults && (
|
| 148 |
+
<>
|
| 149 |
+
{/* Hero Metrics */}
|
| 150 |
+
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-8 animate-fade-in-up delay-100">
|
| 151 |
+
{[
|
| 152 |
+
{
|
| 153 |
+
label: "GraphRAG F1",
|
| 154 |
+
value: (data.graphrag.avgF1 * 100).toFixed(1) + "%",
|
| 155 |
+
delta: `+${((data.graphrag.avgF1 - data.baseline.avgF1) * 100).toFixed(1)}%`,
|
| 156 |
+
color: "#FF6B00",
|
| 157 |
+
bg: "linear-gradient(135deg, #FFF4EB, #faf9f5)",
|
| 158 |
+
},
|
| 159 |
+
{
|
| 160 |
+
label: "Win Rate",
|
| 161 |
+
value: (data.graphragF1WinRate * 100).toFixed(0) + "%",
|
| 162 |
+
delta: "of queries",
|
| 163 |
+
color: "#5db872",
|
| 164 |
+
bg: "linear-gradient(135deg, #ecf7ef, #faf9f5)",
|
| 165 |
+
},
|
| 166 |
+
{
|
| 167 |
+
label: "Bridge F1 Gain",
|
| 168 |
+
value: data.byType.bridge ? `+${((data.byType.bridge.graphragF1 - data.byType.bridge.baselineF1) * 100).toFixed(0)}%` : "N/A",
|
| 169 |
+
delta: "vs baseline",
|
| 170 |
+
color: "#0072CE",
|
| 171 |
+
bg: "linear-gradient(135deg, #E6F4FF, #faf9f5)",
|
| 172 |
+
},
|
| 173 |
+
{
|
| 174 |
+
label: "Samples",
|
| 175 |
+
value: data.numSamples.toString(),
|
| 176 |
+
delta: "HotpotQA",
|
| 177 |
+
color: "#002B49",
|
| 178 |
+
bg: "linear-gradient(135deg, #f5f0e8, #faf9f5)",
|
| 179 |
+
},
|
| 180 |
+
].map((m, i) => (
|
| 181 |
+
<div key={i} className="card-hover" style={{
|
| 182 |
+
background: m.bg, borderRadius: "16px", padding: "28px",
|
| 183 |
+
textAlign: "center",
|
| 184 |
+
}}>
|
| 185 |
+
<div className="metric-value" style={{ color: m.color, fontSize: "2.25rem" }}>{m.value}</div>
|
| 186 |
+
<div className="metric-label mt-1">{m.label}</div>
|
| 187 |
+
<div className="caption mt-2" style={{ color: m.color }}>{m.delta}</div>
|
| 188 |
+
</div>
|
| 189 |
+
))}
|
| 190 |
+
</div>
|
| 191 |
+
|
| 192 |
+
{/* Charts Grid */}
|
| 193 |
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
|
| 194 |
+
{/* Radar */}
|
| 195 |
+
{radarData.length > 0 && (
|
| 196 |
+
<div className="card animate-fade-in-up delay-200">
|
| 197 |
+
<div className="title-md mb-6">Multi-Metric Comparison</div>
|
| 198 |
+
<ResponsiveContainer width="100%" height={360}>
|
| 199 |
+
<RadarChart data={radarData}>
|
| 200 |
+
<PolarGrid stroke="#002B49" strokeOpacity={0.1} />
|
| 201 |
+
<PolarAngleAxis dataKey="metric" tick={{ fill: "#6c6a64", fontSize: 12 }} />
|
| 202 |
+
<Radar name="Baseline" dataKey="Baseline" stroke="#0072CE" fill="#0072CE" fillOpacity={0.12} strokeWidth={2.5} />
|
| 203 |
+
<Radar name="GraphRAG" dataKey="GraphRAG" stroke="#FF6B00" fill="#FF6B00" fillOpacity={0.12} strokeWidth={2.5} />
|
| 204 |
+
<Legend />
|
| 205 |
+
<Tooltip contentStyle={{ background: "#faf9f5", border: "1px solid #e6dfd8", borderRadius: "10px" }} />
|
| 206 |
+
</RadarChart>
|
| 207 |
+
</ResponsiveContainer>
|
| 208 |
+
</div>
|
| 209 |
+
)}
|
| 210 |
+
|
| 211 |
+
{/* F1 by Type */}
|
| 212 |
+
{typeData.length > 0 && (
|
| 213 |
+
<div className="card animate-fade-in-up delay-300">
|
| 214 |
+
<div className="title-md mb-6">F1 Score by Question Type</div>
|
| 215 |
+
<ResponsiveContainer width="100%" height={360}>
|
| 216 |
+
<BarChart data={typeData} margin={{ top: 20, right: 20, left: 0, bottom: 0 }}>
|
| 217 |
+
<CartesianGrid strokeDasharray="3 3" stroke="#002B49" strokeOpacity={0.06} />
|
| 218 |
+
<XAxis dataKey="name" tick={{ fill: "#6c6a64", fontSize: 13 }} />
|
| 219 |
+
<YAxis domain={[0, 100]} tick={{ fill: "#6c6a64", fontSize: 12 }} unit="%" />
|
| 220 |
+
<Tooltip contentStyle={{ background: "#faf9f5", border: "1px solid #e6dfd8", borderRadius: "10px" }} />
|
| 221 |
+
<Legend />
|
| 222 |
+
<Bar dataKey="Baseline" fill="#0072CE" radius={[6, 6, 0, 0]} />
|
| 223 |
+
<Bar dataKey="GraphRAG" fill="#FF6B00" radius={[6, 6, 0, 0]} />
|
| 224 |
+
</BarChart>
|
| 225 |
+
</ResponsiveContainer>
|
| 226 |
+
</div>
|
| 227 |
+
)}
|
| 228 |
+
</div>
|
| 229 |
+
|
| 230 |
+
{/* Token Efficiency */}
|
| 231 |
+
<div className="card mb-8 animate-fade-in-up delay-400">
|
| 232 |
+
<div className="title-md mb-6">Token Usage Breakdown</div>
|
| 233 |
+
<ResponsiveContainer width="100%" height={300}>
|
| 234 |
+
<BarChart data={tokenData} layout="vertical" margin={{ top: 10, right: 30, left: 80, bottom: 0 }}>
|
| 235 |
+
<CartesianGrid strokeDasharray="3 3" stroke="#002B49" strokeOpacity={0.06} />
|
| 236 |
+
<XAxis type="number" tick={{ fill: "#6c6a64", fontSize: 12 }} />
|
| 237 |
+
<YAxis dataKey="name" type="category" tick={{ fill: "#6c6a64", fontSize: 13 }} />
|
| 238 |
+
<Tooltip contentStyle={{ background: "#faf9f5", border: "1px solid #e6dfd8", borderRadius: "10px" }} />
|
| 239 |
+
<Legend />
|
| 240 |
+
<Bar dataKey="Baseline" fill="#0072CE" radius={[0, 6, 6, 0]} barSize={28} />
|
| 241 |
+
<Bar dataKey="GraphRAG" fill="#FF6B00" radius={[0, 6, 6, 0]} barSize={28} />
|
| 242 |
+
</BarChart>
|
| 243 |
+
</ResponsiveContainer>
|
| 244 |
+
</div>
|
| 245 |
+
|
| 246 |
+
{/* Detailed Table */}
|
| 247 |
+
<div className="card mb-8 animate-fade-in-up delay-500">
|
| 248 |
+
<div className="title-md mb-6">Full Comparison Table</div>
|
| 249 |
+
<div className="overflow-x-auto">
|
| 250 |
+
<table style={{ width: "100%", borderCollapse: "collapse", fontSize: "0.9375rem" }}>
|
| 251 |
+
<thead>
|
| 252 |
+
<tr style={{ borderBottom: "2px solid var(--color-hairline)" }}>
|
| 253 |
+
{["Metric", "Baseline RAG", "GraphRAG", "Δ", "Winner"].map(h => (
|
| 254 |
+
<th key={h} className="caption-uppercase text-left" style={{ padding: "14px 16px" }}>{h}</th>
|
| 255 |
+
))}
|
| 256 |
+
</tr>
|
| 257 |
+
</thead>
|
| 258 |
+
<tbody>
|
| 259 |
+
{[
|
| 260 |
+
{
|
| 261 |
+
metric: "Average F1 Score",
|
| 262 |
+
b: data.baseline.avgF1.toFixed(4), g: data.graphrag.avgF1.toFixed(4),
|
| 263 |
+
delta: `+${((data.graphrag.avgF1 - data.baseline.avgF1) * 100).toFixed(1)}%`,
|
| 264 |
+
winner: data.graphrag.avgF1 > data.baseline.avgF1 ? "graphrag" : "baseline",
|
| 265 |
+
},
|
| 266 |
+
{
|
| 267 |
+
metric: "Average Exact Match",
|
| 268 |
+
b: data.baseline.avgEM.toFixed(4), g: data.graphrag.avgEM.toFixed(4),
|
| 269 |
+
delta: `+${((data.graphrag.avgEM - data.baseline.avgEM) * 100).toFixed(1)}%`,
|
| 270 |
+
winner: data.graphrag.avgEM > data.baseline.avgEM ? "graphrag" : "baseline",
|
| 271 |
+
},
|
| 272 |
+
{
|
| 273 |
+
metric: "Avg Tokens/Query",
|
| 274 |
+
b: data.baseline.avgTokens.toLocaleString(), g: data.graphrag.avgTokens.toLocaleString(),
|
| 275 |
+
delta: `${(data.graphrag.avgTokens / data.baseline.avgTokens).toFixed(1)}×`,
|
| 276 |
+
winner: data.baseline.avgTokens < data.graphrag.avgTokens ? "baseline" : "graphrag",
|
| 277 |
+
},
|
| 278 |
+
{
|
| 279 |
+
metric: "Avg Cost/Query",
|
| 280 |
+
b: "$" + data.baseline.avgCost.toFixed(6), g: "$" + data.graphrag.avgCost.toFixed(6),
|
| 281 |
+
delta: `${(data.graphrag.avgCost / data.baseline.avgCost).toFixed(1)}×`,
|
| 282 |
+
winner: data.baseline.avgCost < data.graphrag.avgCost ? "baseline" : "graphrag",
|
| 283 |
+
},
|
| 284 |
+
{
|
| 285 |
+
metric: "Avg Latency",
|
| 286 |
+
b: data.baseline.avgLatency + "ms", g: data.graphrag.avgLatency + "ms",
|
| 287 |
+
delta: `${(data.graphrag.avgLatency / data.baseline.avgLatency).toFixed(1)}×`,
|
| 288 |
+
winner: data.baseline.avgLatency < data.graphrag.avgLatency ? "baseline" : "graphrag",
|
| 289 |
+
},
|
| 290 |
+
{
|
| 291 |
+
metric: "F1 Win Rate",
|
| 292 |
+
b: ((1 - data.graphragF1WinRate) * 100).toFixed(0) + "%",
|
| 293 |
+
g: (data.graphragF1WinRate * 100).toFixed(0) + "%",
|
| 294 |
+
delta: "",
|
| 295 |
+
winner: data.graphragF1WinRate > 0.5 ? "graphrag" : "baseline",
|
| 296 |
+
},
|
| 297 |
+
].map((row, i) => (
|
| 298 |
+
<tr key={i} style={{ borderBottom: "1px solid var(--color-hairline-soft)" }}>
|
| 299 |
+
<td className="title-sm" style={{ padding: "14px 16px" }}>{row.metric}</td>
|
| 300 |
+
<td style={{ padding: "14px 16px", fontFamily: "var(--font-mono)", color: "#0072CE" }}>{row.b}</td>
|
| 301 |
+
<td style={{ padding: "14px 16px", fontFamily: "var(--font-mono)", color: "#FF6B00" }}>{row.g}</td>
|
| 302 |
+
<td style={{ padding: "14px 16px", fontFamily: "var(--font-mono)", color: "var(--color-muted)", fontSize: "0.8125rem" }}>{row.delta}</td>
|
| 303 |
+
<td style={{ padding: "14px 16px" }}>
|
| 304 |
+
<span className={row.winner === "graphrag" ? "badge-orange" : "badge-blue"} style={{ fontSize: "0.6875rem" }}>
|
| 305 |
+
{row.winner === "graphrag" ? "GraphRAG ✓" : "Baseline ✓"}
|
| 306 |
+
</span>
|
| 307 |
+
</td>
|
| 308 |
+
</tr>
|
| 309 |
+
))}
|
| 310 |
+
</tbody>
|
| 311 |
+
</table>
|
| 312 |
+
</div>
|
| 313 |
+
</div>
|
| 314 |
+
|
| 315 |
+
{/* Insight */}
|
| 316 |
+
<div className="card-coral animate-fade-in-up delay-600">
|
| 317 |
+
<div className="display-sm" style={{ color: "white" }}>💡 Key Finding</div>
|
| 318 |
+
<p className="body-lg mt-4" style={{ color: "rgba(255,255,255,0.9)", maxWidth: "640px" }}>
|
| 319 |
+
GraphRAG achieves <strong>+{((data.graphrag.avgF1 - data.baseline.avgF1) * 100).toFixed(0)}% higher F1</strong> on
|
| 320 |
+
multi-hop questions, with the biggest gains on <strong>bridge queries</strong> where graph
|
| 321 |
+
traversal connects entities through shared relationships.
|
| 322 |
+
</p>
|
| 323 |
+
<p className="body-md mt-3" style={{ color: "rgba(255,255,255,0.7)" }}>
|
| 324 |
+
The Adaptive Router can eliminate the token overhead for simple queries by routing them
|
| 325 |
+
to Baseline RAG — achieving the best of both worlds.
|
| 326 |
+
</p>
|
| 327 |
+
</div>
|
| 328 |
+
</>
|
| 329 |
+
)}
|
| 330 |
+
|
| 331 |
+
{/* Report */}
|
| 332 |
+
{report && (
|
| 333 |
+
<div className="code-window mt-8 animate-fade-in-up delay-700">
|
| 334 |
+
<div className="code-window-header">
|
| 335 |
+
<div className="code-window-dot code-window-dot-red" />
|
| 336 |
+
<div className="code-window-dot code-window-dot-yellow" />
|
| 337 |
+
<div className="code-window-dot code-window-dot-green" />
|
| 338 |
+
<span className="body-sm" style={{ color: "#a09d96", marginLeft: "12px" }}>benchmark_report.txt</span>
|
| 339 |
+
</div>
|
| 340 |
+
<pre className="code-window-body" style={{ whiteSpace: "pre-wrap", fontSize: "0.8125rem" }}>
|
| 341 |
+
{report}
|
| 342 |
+
</pre>
|
| 343 |
+
</div>
|
| 344 |
+
)}
|
| 345 |
+
</div>
|
| 346 |
+
);
|
| 347 |
+
}
|
web/src/components/docs/DocsContent.tsx
ADDED
|
@@ -0,0 +1,379 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { useState } from "react";
|
| 4 |
+
|
| 5 |
+
const SECTIONS = [
|
| 6 |
+
{
|
| 7 |
+
id: "quickstart",
|
| 8 |
+
title: "Quick Start",
|
| 9 |
+
icon: "🚀",
|
| 10 |
+
content: [
|
| 11 |
+
{
|
| 12 |
+
heading: "Prerequisites",
|
| 13 |
+
body: `Before running GraphRAG, make sure you have:
|
| 14 |
+
- Python 3.10+
|
| 15 |
+
- Node.js 18+
|
| 16 |
+
- Docker (optional, for containerized deployment)
|
| 17 |
+
- A TigerGraph Cloud account (free tier available)
|
| 18 |
+
- At least one LLM API key (Claude, OpenAI, etc.)`,
|
| 19 |
+
},
|
| 20 |
+
{
|
| 21 |
+
heading: "Installation",
|
| 22 |
+
code: `# Clone the repository
|
| 23 |
+
git clone https://github.com/MUTHUKUMARAN-K-1/graphrag-inference-hackathon
|
| 24 |
+
cd graphrag-inference-hackathon
|
| 25 |
+
|
| 26 |
+
# Backend setup
|
| 27 |
+
pip install -r requirements.txt
|
| 28 |
+
|
| 29 |
+
# Frontend setup
|
| 30 |
+
cd web
|
| 31 |
+
npm install
|
| 32 |
+
npm run dev
|
| 33 |
+
|
| 34 |
+
# Or use Docker
|
| 35 |
+
docker build -t graphrag .
|
| 36 |
+
docker run -p 3000:3000 -p 8000:8000 graphrag`,
|
| 37 |
+
},
|
| 38 |
+
{
|
| 39 |
+
heading: "Environment Variables",
|
| 40 |
+
code: `# .env file
|
| 41 |
+
TIGERGRAPH_HOST=https://your-instance.i.tgcloud.io
|
| 42 |
+
TIGERGRAPH_GRAPH=GraphRAG
|
| 43 |
+
TIGERGRAPH_SECRET=your_secret
|
| 44 |
+
|
| 45 |
+
# LLM Providers (set at least one)
|
| 46 |
+
ANTHROPIC_API_KEY=sk-ant-...
|
| 47 |
+
OPENAI_API_KEY=sk-...
|
| 48 |
+
GEMINI_API_KEY=AI...
|
| 49 |
+
GROQ_API_KEY=gsk_...`,
|
| 50 |
+
},
|
| 51 |
+
],
|
| 52 |
+
},
|
| 53 |
+
{
|
| 54 |
+
id: "api",
|
| 55 |
+
title: "API Reference",
|
| 56 |
+
icon: "📡",
|
| 57 |
+
content: [
|
| 58 |
+
{
|
| 59 |
+
heading: "POST /api/compare",
|
| 60 |
+
body: "Run a side-by-side comparison of Baseline RAG and GraphRAG pipelines.",
|
| 61 |
+
code: `// Request
|
| 62 |
+
POST /api/compare
|
| 63 |
+
{
|
| 64 |
+
"query": "Were Scott Derrickson and Ed Wood of the same nationality?",
|
| 65 |
+
"provider": "anthropic",
|
| 66 |
+
"model": "claude-sonnet-4-20250514",
|
| 67 |
+
"adaptiveRouting": true
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
// Response
|
| 71 |
+
{
|
| 72 |
+
"baseline": {
|
| 73 |
+
"answer": "Yes, both are American.",
|
| 74 |
+
"tokens": 950,
|
| 75 |
+
"latencyMs": 1200,
|
| 76 |
+
"costUsd": 0.003800,
|
| 77 |
+
"entities": [],
|
| 78 |
+
"relations": []
|
| 79 |
+
},
|
| 80 |
+
"graphrag": {
|
| 81 |
+
"answer": "Yes, both directors are American...",
|
| 82 |
+
"tokens": 2400,
|
| 83 |
+
"latencyMs": 1800,
|
| 84 |
+
"costUsd": 0.009600,
|
| 85 |
+
"entities": ["Scott Derrickson", "Ed Wood", "United States"],
|
| 86 |
+
"relations": ["BORN_IN", "LOCATED_IN"]
|
| 87 |
+
},
|
| 88 |
+
"complexity": 0.72,
|
| 89 |
+
"queryType": "bridge",
|
| 90 |
+
"recommended": "graphrag"
|
| 91 |
+
}`,
|
| 92 |
+
},
|
| 93 |
+
{
|
| 94 |
+
heading: "POST /api/benchmark",
|
| 95 |
+
body: "Run batch evaluation on HotpotQA samples.",
|
| 96 |
+
code: `// Request
|
| 97 |
+
POST /api/benchmark
|
| 98 |
+
{ "numSamples": 10 }
|
| 99 |
+
|
| 100 |
+
// Response
|
| 101 |
+
{
|
| 102 |
+
"aggregate": {
|
| 103 |
+
"numSamples": 10,
|
| 104 |
+
"baseline": { "avgF1": 0.6234, "avgEM": 0.4000, ... },
|
| 105 |
+
"graphrag": { "avgF1": 0.7567, "avgEM": 0.5000, ... },
|
| 106 |
+
"graphragF1WinRate": 0.70,
|
| 107 |
+
"byType": {
|
| 108 |
+
"bridge": { "count": 5, "baselineF1": 0.58, "graphragF1": 0.79 },
|
| 109 |
+
"comparison": { "count": 5, "baselineF1": 0.67, "graphragF1": 0.72 }
|
| 110 |
+
}
|
| 111 |
+
}
|
| 112 |
+
}`,
|
| 113 |
+
},
|
| 114 |
+
{
|
| 115 |
+
heading: "GET /api/providers",
|
| 116 |
+
body: "List all available LLM providers and their models.",
|
| 117 |
+
code: `// Response
|
| 118 |
+
{
|
| 119 |
+
"providers": [
|
| 120 |
+
{
|
| 121 |
+
"id": "anthropic",
|
| 122 |
+
"name": "Anthropic Claude",
|
| 123 |
+
"isLocal": false,
|
| 124 |
+
"hasApiKey": true,
|
| 125 |
+
"defaultModel": "claude-sonnet-4-20250514",
|
| 126 |
+
"models": [
|
| 127 |
+
{ "id": "claude-sonnet-4-20250514", "name": "Claude Sonnet 4", "speed": "medium", "quality": "high" }
|
| 128 |
+
]
|
| 129 |
+
},
|
| 130 |
+
...
|
| 131 |
+
]
|
| 132 |
+
}`,
|
| 133 |
+
},
|
| 134 |
+
],
|
| 135 |
+
},
|
| 136 |
+
{
|
| 137 |
+
id: "architecture",
|
| 138 |
+
title: "Architecture",
|
| 139 |
+
icon: "🏗️",
|
| 140 |
+
content: [
|
| 141 |
+
{
|
| 142 |
+
heading: "4-Layer AI Factory Model",
|
| 143 |
+
body: `The system follows an AI Factory architecture with four distinct layers:
|
| 144 |
+
|
| 145 |
+
**Layer 1 — Graph Layer (TigerGraph Cloud)**
|
| 146 |
+
Stores the knowledge graph with typed vertices and edges. Supports GSQL queries for multi-hop traversal.
|
| 147 |
+
|
| 148 |
+
**Layer 2 — Orchestration Layer**
|
| 149 |
+
Routes queries through the Adaptive Router, which scores complexity and classifies query types (bridge, comparison, factoid).
|
| 150 |
+
|
| 151 |
+
**Layer 3 — LLM Layer (12 Providers)**
|
| 152 |
+
Universal LLM abstraction supporting Claude, GPT-4, Gemini, Llama, Mistral, DeepSeek, Grok, Cohere, and more.
|
| 153 |
+
|
| 154 |
+
**Layer 4 — Evaluation Layer (RAGAS)**
|
| 155 |
+
Automated evaluation with F1, Exact Match, token counting, cost tracking, and latency measurement.`,
|
| 156 |
+
},
|
| 157 |
+
{
|
| 158 |
+
heading: "Dual Pipeline Design",
|
| 159 |
+
body: `Every query runs through two pipelines simultaneously:
|
| 160 |
+
|
| 161 |
+
**Pipeline A — Baseline RAG**
|
| 162 |
+
Query → Vector similarity search → Retrieved passages → LLM generation
|
| 163 |
+
- Fast and cheap
|
| 164 |
+
- Best for simple factoid queries
|
| 165 |
+
|
| 166 |
+
**Pipeline B — GraphRAG**
|
| 167 |
+
Query → Entity extraction → Graph traversal → Structured evidence → LLM generation
|
| 168 |
+
- More tokens, but higher accuracy
|
| 169 |
+
- Best for multi-hop bridge and comparison questions
|
| 170 |
+
- Provides traceable reasoning paths`,
|
| 171 |
+
},
|
| 172 |
+
],
|
| 173 |
+
},
|
| 174 |
+
{
|
| 175 |
+
id: "novelties",
|
| 176 |
+
title: "Novel Features",
|
| 177 |
+
icon: "✨",
|
| 178 |
+
content: [
|
| 179 |
+
{
|
| 180 |
+
heading: "1. Adaptive Query Router",
|
| 181 |
+
body: `Analyzes query complexity (0.0–1.0) using:
|
| 182 |
+
- Entity count — more entities = higher complexity
|
| 183 |
+
- Multi-hop indicators — "both", "same", "compared to"
|
| 184 |
+
- Question word analysis — "which" and "who" patterns
|
| 185 |
+
- Dependency chain length
|
| 186 |
+
|
| 187 |
+
Routes simple queries to Baseline RAG (fast, cheap) and complex queries to GraphRAG (precise, traceable).`,
|
| 188 |
+
},
|
| 189 |
+
{
|
| 190 |
+
heading: "2. Schema-Bounded Extraction",
|
| 191 |
+
body: `Traditional NER extracts arbitrary entity types. Our system constrains extraction to TigerGraph's actual schema:
|
| 192 |
+
- Only PERSON, LOCATION, WORK, CONCEPT, etc. (valid vertex types)
|
| 193 |
+
- Eliminates hallucinated node types that would fail on graph lookup
|
| 194 |
+
- Ensures every extracted entity maps to a real vertex in the graph`,
|
| 195 |
+
},
|
| 196 |
+
{
|
| 197 |
+
heading: "3. Dual-Level Keywords",
|
| 198 |
+
body: `Extracts keywords at two granularity levels:
|
| 199 |
+
- **High-level**: Concepts, themes, categories (e.g., "nationality", "American cinema")
|
| 200 |
+
- **Low-level**: Specific entities, names, dates (e.g., "Scott Derrickson", "1962")
|
| 201 |
+
Enables graph traversal at multiple levels for richer context retrieval.`,
|
| 202 |
+
},
|
| 203 |
+
{
|
| 204 |
+
heading: "4. Graph Reasoning Paths",
|
| 205 |
+
body: `Traces explicit entity→relation→entity chains:
|
| 206 |
+
- Scott Derrickson → BORN_IN → Denver, CO → LOCATED_IN → United States
|
| 207 |
+
- Ed Wood → BORN_IN → Poughkeepsie, NY → LOCATED_IN → United States
|
| 208 |
+
|
| 209 |
+
These paths are included in the LLM prompt as structured evidence, making answers verifiable and explainable.`,
|
| 210 |
+
},
|
| 211 |
+
{
|
| 212 |
+
heading: "5. Real-Time Cost Tracking",
|
| 213 |
+
body: `Measures per-query economics:
|
| 214 |
+
- Input/output tokens counted per provider's tokenization
|
| 215 |
+
- USD cost calculated using current provider pricing
|
| 216 |
+
- Latency measured end-to-end (graph + LLM)
|
| 217 |
+
- Interactive projections: "What would 100K queries/month cost?"`,
|
| 218 |
+
},
|
| 219 |
+
],
|
| 220 |
+
},
|
| 221 |
+
{
|
| 222 |
+
id: "deployment",
|
| 223 |
+
title: "Deployment",
|
| 224 |
+
icon: "🐳",
|
| 225 |
+
content: [
|
| 226 |
+
{
|
| 227 |
+
heading: "Docker Deployment",
|
| 228 |
+
code: `# Build the image
|
| 229 |
+
docker build -t graphrag .
|
| 230 |
+
|
| 231 |
+
# Run with environment variables
|
| 232 |
+
docker run -d \\
|
| 233 |
+
-p 3000:3000 \\
|
| 234 |
+
-p 8000:8000 \\
|
| 235 |
+
-e TIGERGRAPH_HOST=https://your-instance.i.tgcloud.io \\
|
| 236 |
+
-e ANTHROPIC_API_KEY=sk-ant-... \\
|
| 237 |
+
graphrag`,
|
| 238 |
+
},
|
| 239 |
+
{
|
| 240 |
+
heading: "TigerGraph Cloud Setup",
|
| 241 |
+
body: `1. Create a free account at tgcloud.io
|
| 242 |
+
2. Create a new cluster (free tier: 50MB storage)
|
| 243 |
+
3. Install the GraphRAG schema:
|
| 244 |
+
- Go to GraphStudio
|
| 245 |
+
- Import the schema from graphrag/setup_tigergraph.py
|
| 246 |
+
4. Set TIGERGRAPH_HOST, TIGERGRAPH_GRAPH, TIGERGRAPH_SECRET in .env`,
|
| 247 |
+
},
|
| 248 |
+
{
|
| 249 |
+
heading: "Running Locally",
|
| 250 |
+
code: `# Backend (Python)
|
| 251 |
+
cd graphrag-inference-hackathon
|
| 252 |
+
pip install -r requirements.txt
|
| 253 |
+
python -m graphrag.main
|
| 254 |
+
|
| 255 |
+
# Frontend (Next.js)
|
| 256 |
+
cd web
|
| 257 |
+
npm install
|
| 258 |
+
npm run dev
|
| 259 |
+
|
| 260 |
+
# Open http://localhost:3000`,
|
| 261 |
+
},
|
| 262 |
+
],
|
| 263 |
+
},
|
| 264 |
+
];
|
| 265 |
+
|
| 266 |
+
export function DocsContent() {
|
| 267 |
+
const [activeSection, setActiveSection] = useState("quickstart");
|
| 268 |
+
|
| 269 |
+
const section = SECTIONS.find(s => s.id === activeSection) || SECTIONS[0];
|
| 270 |
+
|
| 271 |
+
return (
|
| 272 |
+
<div style={{ display: "flex", minHeight: "calc(100vh - 72px)" }}>
|
| 273 |
+
{/* Sidebar */}
|
| 274 |
+
<aside style={{
|
| 275 |
+
width: "260px",
|
| 276 |
+
flexShrink: 0,
|
| 277 |
+
borderRight: "1px solid var(--color-hairline-soft)",
|
| 278 |
+
padding: "32px 0",
|
| 279 |
+
position: "sticky",
|
| 280 |
+
top: "72px",
|
| 281 |
+
height: "calc(100vh - 72px)",
|
| 282 |
+
overflowY: "auto",
|
| 283 |
+
display: "none",
|
| 284 |
+
}} className="lg:!block">
|
| 285 |
+
<div style={{ padding: "0 24px" }}>
|
| 286 |
+
<div className="caption-uppercase mb-4" style={{ color: "var(--color-tiger-orange)" }}>Documentation</div>
|
| 287 |
+
<nav className="flex flex-col gap-1">
|
| 288 |
+
{SECTIONS.map((s) => (
|
| 289 |
+
<button
|
| 290 |
+
key={s.id}
|
| 291 |
+
onClick={() => setActiveSection(s.id)}
|
| 292 |
+
style={{
|
| 293 |
+
display: "flex",
|
| 294 |
+
alignItems: "center",
|
| 295 |
+
gap: "10px",
|
| 296 |
+
padding: "10px 14px",
|
| 297 |
+
borderRadius: "8px",
|
| 298 |
+
border: "none",
|
| 299 |
+
background: activeSection === s.id ? "var(--color-tiger-orange-light)" : "transparent",
|
| 300 |
+
color: activeSection === s.id ? "var(--color-tiger-orange)" : "var(--color-muted)",
|
| 301 |
+
fontWeight: activeSection === s.id ? 600 : 400,
|
| 302 |
+
fontSize: "0.875rem",
|
| 303 |
+
cursor: "pointer",
|
| 304 |
+
textAlign: "left",
|
| 305 |
+
transition: "all 0.15s ease",
|
| 306 |
+
width: "100%",
|
| 307 |
+
}}
|
| 308 |
+
>
|
| 309 |
+
<span>{s.icon}</span>
|
| 310 |
+
{s.title}
|
| 311 |
+
</button>
|
| 312 |
+
))}
|
| 313 |
+
</nav>
|
| 314 |
+
</div>
|
| 315 |
+
</aside>
|
| 316 |
+
|
| 317 |
+
{/* Mobile Tabs */}
|
| 318 |
+
<div className="lg:hidden" style={{
|
| 319 |
+
position: "fixed", bottom: 0, left: 0, right: 0, zIndex: 40,
|
| 320 |
+
background: "var(--color-canvas)",
|
| 321 |
+
borderTop: "1px solid var(--color-hairline)",
|
| 322 |
+
display: "flex",
|
| 323 |
+
overflowX: "auto",
|
| 324 |
+
padding: "8px",
|
| 325 |
+
gap: "4px",
|
| 326 |
+
}}>
|
| 327 |
+
{SECTIONS.map((s) => (
|
| 328 |
+
<button
|
| 329 |
+
key={s.id}
|
| 330 |
+
onClick={() => setActiveSection(s.id)}
|
| 331 |
+
className={activeSection === s.id ? "badge-orange" : "badge-outline"}
|
| 332 |
+
style={{ fontSize: "0.6875rem", whiteSpace: "nowrap", cursor: "pointer", border: "none" }}
|
| 333 |
+
>
|
| 334 |
+
{s.icon} {s.title}
|
| 335 |
+
</button>
|
| 336 |
+
))}
|
| 337 |
+
</div>
|
| 338 |
+
|
| 339 |
+
{/* Content */}
|
| 340 |
+
<main style={{ flex: 1, padding: "48px 40px 96px", maxWidth: "840px" }}>
|
| 341 |
+
<div className="flex items-center gap-3 mb-2">
|
| 342 |
+
<span style={{ fontSize: "1.5rem" }}>{section.icon}</span>
|
| 343 |
+
<h1 className="display-lg">{section.title}</h1>
|
| 344 |
+
</div>
|
| 345 |
+
<div className="divider mb-8" />
|
| 346 |
+
|
| 347 |
+
<div className="flex flex-col gap-10">
|
| 348 |
+
{section.content.map((block, i) => (
|
| 349 |
+
<div key={i}>
|
| 350 |
+
<h2 className="display-sm mb-4">{block.heading}</h2>
|
| 351 |
+
{block.body && (
|
| 352 |
+
<div className="body-lg" style={{ color: "var(--color-body)", lineHeight: 1.75, whiteSpace: "pre-line" }}>
|
| 353 |
+
{block.body.split(/(\*\*[^*]+\*\*)/).map((part, j) => {
|
| 354 |
+
if (part.startsWith("**") && part.endsWith("**")) {
|
| 355 |
+
return <strong key={j} style={{ color: "var(--color-ink)" }}>{part.slice(2, -2)}</strong>;
|
| 356 |
+
}
|
| 357 |
+
return <span key={j}>{part}</span>;
|
| 358 |
+
})}
|
| 359 |
+
</div>
|
| 360 |
+
)}
|
| 361 |
+
{block.code && (
|
| 362 |
+
<div className="code-window mt-4">
|
| 363 |
+
<div className="code-window-header">
|
| 364 |
+
<div className="code-window-dot code-window-dot-red" />
|
| 365 |
+
<div className="code-window-dot code-window-dot-yellow" />
|
| 366 |
+
<div className="code-window-dot code-window-dot-green" />
|
| 367 |
+
</div>
|
| 368 |
+
<pre className="code-window-body" style={{ fontSize: "0.8125rem", lineHeight: 1.7, whiteSpace: "pre-wrap" }}>
|
| 369 |
+
{block.code}
|
| 370 |
+
</pre>
|
| 371 |
+
</div>
|
| 372 |
+
)}
|
| 373 |
+
</div>
|
| 374 |
+
))}
|
| 375 |
+
</div>
|
| 376 |
+
</main>
|
| 377 |
+
</div>
|
| 378 |
+
);
|
| 379 |
+
}
|
web/src/components/explorer/ExplorerContent.tsx
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { useState, useMemo } from "react";
|
| 4 |
+
|
| 5 |
+
interface GraphNode {
|
| 6 |
+
id: string;
|
| 7 |
+
label: string;
|
| 8 |
+
type: string;
|
| 9 |
+
x: number;
|
| 10 |
+
y: number;
|
| 11 |
+
description?: string;
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
interface GraphEdge {
|
| 15 |
+
source: string;
|
| 16 |
+
target: string;
|
| 17 |
+
label: string;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
const TYPE_COLORS: Record<string, string> = {
|
| 21 |
+
PERSON: "#FF6B6B",
|
| 22 |
+
ORGANIZATION: "#4ECDC4",
|
| 23 |
+
LOCATION: "#45B7D1",
|
| 24 |
+
EVENT: "#FFA07A",
|
| 25 |
+
DATE: "#98D8C8",
|
| 26 |
+
CONCEPT: "#AED6F1",
|
| 27 |
+
WORK: "#F9E79F",
|
| 28 |
+
QUERY: "#FF6B00",
|
| 29 |
+
};
|
| 30 |
+
|
| 31 |
+
const SCENARIOS: {
|
| 32 |
+
name: string;
|
| 33 |
+
query: string;
|
| 34 |
+
nodes: GraphNode[];
|
| 35 |
+
edges: GraphEdge[];
|
| 36 |
+
reasoning: string[];
|
| 37 |
+
}[] = [
|
| 38 |
+
{
|
| 39 |
+
name: "Nationality Comparison",
|
| 40 |
+
query: "Were Scott Derrickson and Ed Wood of the same nationality?",
|
| 41 |
+
nodes: [
|
| 42 |
+
{ id: "q", label: "Query", type: "QUERY", x: 450, y: 50, description: "Bridge question comparing two entities" },
|
| 43 |
+
{ id: "sd", label: "Scott Derrickson", type: "PERSON", x: 200, y: 190, description: "American filmmaker, director of Sinister (2012)" },
|
| 44 |
+
{ id: "ew", label: "Ed Wood", type: "PERSON", x: 700, y: 190, description: "American filmmaker, director of Plan 9 from Outer Space" },
|
| 45 |
+
{ id: "us", label: "United States", type: "LOCATION", x: 450, y: 340, description: "Country — shared nationality node" },
|
| 46 |
+
{ id: "denver", label: "Denver, CO", type: "LOCATION", x: 120, y: 340, description: "Birthplace of Scott Derrickson" },
|
| 47 |
+
{ id: "pough", label: "Poughkeepsie, NY", type: "LOCATION", x: 780, y: 340, description: "Birthplace of Ed Wood" },
|
| 48 |
+
{ id: "sinister", label: "Sinister (2012)", type: "WORK", x: 100, y: 200, description: "Horror film directed by Derrickson" },
|
| 49 |
+
{ id: "planNine", label: "Plan 9 from Outer Space", type: "WORK", x: 800, y: 200, description: "Cult classic by Ed Wood" },
|
| 50 |
+
{ id: "horror", label: "Horror Genre", type: "CONCEPT", x: 450, y: 460, description: "Shared genre concept" },
|
| 51 |
+
],
|
| 52 |
+
edges: [
|
| 53 |
+
{ source: "q", target: "sd", label: "FOUND_ENTITY" },
|
| 54 |
+
{ source: "q", target: "ew", label: "FOUND_ENTITY" },
|
| 55 |
+
{ source: "sd", target: "denver", label: "BORN_IN" },
|
| 56 |
+
{ source: "ew", target: "pough", label: "BORN_IN" },
|
| 57 |
+
{ source: "denver", target: "us", label: "LOCATED_IN" },
|
| 58 |
+
{ source: "pough", target: "us", label: "LOCATED_IN" },
|
| 59 |
+
{ source: "sd", target: "sinister", label: "DIRECTED" },
|
| 60 |
+
{ source: "ew", target: "planNine", label: "DIRECTED" },
|
| 61 |
+
{ source: "sinister", target: "horror", label: "GENRE" },
|
| 62 |
+
{ source: "planNine", target: "horror", label: "GENRE" },
|
| 63 |
+
],
|
| 64 |
+
reasoning: [
|
| 65 |
+
"Entry: Query identifies two key entities — Scott Derrickson and Ed Wood",
|
| 66 |
+
"Hop 1: BORN_IN relationships — Derrickson → Denver, CO; Wood → Poughkeepsie, NY",
|
| 67 |
+
"Hop 2: LOCATED_IN traversal — Both cities → United States",
|
| 68 |
+
"Convergence: Both paths meet at 'United States' node — same nationality confirmed",
|
| 69 |
+
],
|
| 70 |
+
},
|
| 71 |
+
{
|
| 72 |
+
name: "Magazine Comparison",
|
| 73 |
+
query: "Which magazine was started first, Arthur's Magazine or First for Women?",
|
| 74 |
+
nodes: [
|
| 75 |
+
{ id: "q", label: "Query", type: "QUERY", x: 450, y: 50, description: "Comparison question — temporal ordering" },
|
| 76 |
+
{ id: "am", label: "Arthur's Magazine", type: "WORK", x: 220, y: 200, description: "American literary periodical" },
|
| 77 |
+
{ id: "fw", label: "First for Women", type: "WORK", x: 680, y: 200, description: "American women's magazine" },
|
| 78 |
+
{ id: "d1", label: "1844", type: "DATE", x: 220, y: 350, description: "Year Arthur's Magazine was founded" },
|
| 79 |
+
{ id: "d2", label: "1989", type: "DATE", x: 680, y: 350, description: "Year First for Women was founded" },
|
| 80 |
+
{ id: "pub", label: "Publishing", type: "CONCEPT", x: 450, y: 280, description: "Industry category" },
|
| 81 |
+
{ id: "usa", label: "United States", type: "LOCATION", x: 450, y: 420, description: "Country of publication" },
|
| 82 |
+
],
|
| 83 |
+
edges: [
|
| 84 |
+
{ source: "q", target: "am", label: "FOUND_ENTITY" },
|
| 85 |
+
{ source: "q", target: "fw", label: "FOUND_ENTITY" },
|
| 86 |
+
{ source: "am", target: "d1", label: "FOUNDED_IN" },
|
| 87 |
+
{ source: "fw", target: "d2", label: "FOUNDED_IN" },
|
| 88 |
+
{ source: "am", target: "pub", label: "INDUSTRY" },
|
| 89 |
+
{ source: "fw", target: "pub", label: "INDUSTRY" },
|
| 90 |
+
{ source: "am", target: "usa", label: "PUBLISHED_IN" },
|
| 91 |
+
{ source: "fw", target: "usa", label: "PUBLISHED_IN" },
|
| 92 |
+
],
|
| 93 |
+
reasoning: [
|
| 94 |
+
"Entry: Two entities identified — Arthur's Magazine, First for Women",
|
| 95 |
+
"Hop 1: FOUNDED_IN dates — Arthur's → 1844; First for Women → 1989",
|
| 96 |
+
"Comparison: 1844 < 1989 — Arthur's Magazine predates by 145 years",
|
| 97 |
+
"Answer: Arthur's Magazine was started first",
|
| 98 |
+
],
|
| 99 |
+
},
|
| 100 |
+
];
|
| 101 |
+
|
| 102 |
+
export function ExplorerContent() {
|
| 103 |
+
const [scenarioIdx, setScenarioIdx] = useState(0);
|
| 104 |
+
const [selectedNode, setSelectedNode] = useState<string | null>(null);
|
| 105 |
+
const [hops, setHops] = useState(2);
|
| 106 |
+
|
| 107 |
+
const scenario = SCENARIOS[scenarioIdx];
|
| 108 |
+
const { nodes, edges, reasoning } = scenario;
|
| 109 |
+
|
| 110 |
+
const nodeMap = useMemo(() => {
|
| 111 |
+
const map: Record<string, GraphNode> = {};
|
| 112 |
+
nodes.forEach((n) => { map[n.id] = n; });
|
| 113 |
+
return map;
|
| 114 |
+
}, [nodes]);
|
| 115 |
+
|
| 116 |
+
const selectedInfo = selectedNode ? nodeMap[selectedNode] : null;
|
| 117 |
+
|
| 118 |
+
const connectedEdges = selectedNode
|
| 119 |
+
? edges.filter(e => e.source === selectedNode || e.target === selectedNode)
|
| 120 |
+
: [];
|
| 121 |
+
|
| 122 |
+
return (
|
| 123 |
+
<div>
|
| 124 |
+
{/* Scenario Selector */}
|
| 125 |
+
<div className="card mb-6 animate-fade-in-up">
|
| 126 |
+
<div className="flex flex-col md:flex-row gap-6 items-start md:items-end">
|
| 127 |
+
<div className="flex-1">
|
| 128 |
+
<div className="caption-uppercase mb-2" style={{ color: "var(--color-tiger-orange)" }}>Scenario</div>
|
| 129 |
+
<div className="flex gap-3">
|
| 130 |
+
{SCENARIOS.map((s, i) => (
|
| 131 |
+
<button
|
| 132 |
+
key={i}
|
| 133 |
+
className={`btn ${i === scenarioIdx ? "btn-primary" : "btn-secondary"} btn-sm`}
|
| 134 |
+
onClick={() => { setScenarioIdx(i); setSelectedNode(null); }}
|
| 135 |
+
>
|
| 136 |
+
{s.name}
|
| 137 |
+
</button>
|
| 138 |
+
))}
|
| 139 |
+
</div>
|
| 140 |
+
<div className="body-md mt-3" style={{ fontStyle: "italic", color: "var(--color-muted)" }}>
|
| 141 |
+
“{scenario.query}”
|
| 142 |
+
</div>
|
| 143 |
+
</div>
|
| 144 |
+
<div className="flex items-center gap-4">
|
| 145 |
+
<label className="caption whitespace-nowrap flex items-center gap-2">
|
| 146 |
+
Hops: <strong style={{ color: "var(--color-tiger-orange)", fontFamily: "var(--font-mono)" }}>{hops}</strong>
|
| 147 |
+
<input type="range" min={1} max={4} step={1} value={hops}
|
| 148 |
+
onChange={(e) => setHops(+e.target.value)}
|
| 149 |
+
className="w-24 accent-[#FF6B00]" />
|
| 150 |
+
</label>
|
| 151 |
+
</div>
|
| 152 |
+
</div>
|
| 153 |
+
</div>
|
| 154 |
+
|
| 155 |
+
<div className="grid grid-cols-1 xl:grid-cols-4 gap-6">
|
| 156 |
+
{/* Graph Visualization — 3 cols */}
|
| 157 |
+
<div className="xl:col-span-3">
|
| 158 |
+
<div className="card animate-scale-in" style={{ padding: "16px" }}>
|
| 159 |
+
<svg viewBox="0 0 900 520" style={{ width: "100%", height: "100%", minHeight: "480px" }}>
|
| 160 |
+
<defs>
|
| 161 |
+
<filter id="glow">
|
| 162 |
+
<feGaussianBlur stdDeviation="4" result="coloredBlur"/>
|
| 163 |
+
<feMerge>
|
| 164 |
+
<feMergeNode in="coloredBlur"/>
|
| 165 |
+
<feMergeNode in="SourceGraphic"/>
|
| 166 |
+
</feMerge>
|
| 167 |
+
</filter>
|
| 168 |
+
<marker id="arrowhead" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto">
|
| 169 |
+
<polygon points="0 0, 10 3.5, 0 7" fill="#e6dfd8" />
|
| 170 |
+
</marker>
|
| 171 |
+
</defs>
|
| 172 |
+
|
| 173 |
+
{/* Edges */}
|
| 174 |
+
{edges.map((edge, i) => {
|
| 175 |
+
const s = nodeMap[edge.source];
|
| 176 |
+
const t = nodeMap[edge.target];
|
| 177 |
+
if (!s || !t) return null;
|
| 178 |
+
const isConnected = selectedNode && (edge.source === selectedNode || edge.target === selectedNode);
|
| 179 |
+
const isHighlighted = !selectedNode || isConnected;
|
| 180 |
+
const mx = (s.x + t.x) / 2;
|
| 181 |
+
const my = (s.y + t.y) / 2 - 8;
|
| 182 |
+
return (
|
| 183 |
+
<g key={`edge-${i}`}>
|
| 184 |
+
<line
|
| 185 |
+
x1={s.x} y1={s.y} x2={t.x} y2={t.y}
|
| 186 |
+
stroke={isConnected ? "#FF6B00" : "#e6dfd8"}
|
| 187 |
+
strokeWidth={isConnected ? 3 : 1.5}
|
| 188 |
+
strokeOpacity={isHighlighted ? 0.85 : 0.2}
|
| 189 |
+
markerEnd={isConnected ? undefined : "url(#arrowhead)"}
|
| 190 |
+
/>
|
| 191 |
+
{isHighlighted && (
|
| 192 |
+
<text x={mx} y={my} textAnchor="middle" fontSize="9" fill={isConnected ? "#FF6B00" : "#8e8b82"}
|
| 193 |
+
fontFamily="var(--font-mono)" fontWeight={isConnected ? 600 : 400}>
|
| 194 |
+
{edge.label}
|
| 195 |
+
</text>
|
| 196 |
+
)}
|
| 197 |
+
</g>
|
| 198 |
+
);
|
| 199 |
+
})}
|
| 200 |
+
|
| 201 |
+
{/* Nodes */}
|
| 202 |
+
{nodes.map((node) => {
|
| 203 |
+
const color = TYPE_COLORS[node.type] || "#AED6F1";
|
| 204 |
+
const isSelected = selectedNode === node.id;
|
| 205 |
+
const isConnected = connectedEdges.some(e => e.source === node.id || e.target === node.id);
|
| 206 |
+
const r = isSelected ? 26 : 20;
|
| 207 |
+
const dimmed = selectedNode && !isSelected && !isConnected;
|
| 208 |
+
return (
|
| 209 |
+
<g
|
| 210 |
+
key={node.id}
|
| 211 |
+
className="graph-node cursor-pointer"
|
| 212 |
+
onClick={() => setSelectedNode(isSelected ? null : node.id)}
|
| 213 |
+
opacity={dimmed ? 0.3 : 1}
|
| 214 |
+
>
|
| 215 |
+
{isSelected && (
|
| 216 |
+
<>
|
| 217 |
+
<circle cx={node.x} cy={node.y} r={r + 12} fill={color} opacity={0.12} />
|
| 218 |
+
<circle cx={node.x} cy={node.y} r={r + 6} fill={color} opacity={0.08}
|
| 219 |
+
filter="url(#glow)" />
|
| 220 |
+
</>
|
| 221 |
+
)}
|
| 222 |
+
<circle
|
| 223 |
+
cx={node.x} cy={node.y} r={r}
|
| 224 |
+
fill={color}
|
| 225 |
+
stroke="white" strokeWidth="3"
|
| 226 |
+
/>
|
| 227 |
+
{node.type === "QUERY" && (
|
| 228 |
+
<text x={node.x} y={node.y + 4} textAnchor="middle"
|
| 229 |
+
fontSize="14" fill="white" fontWeight="bold">?</text>
|
| 230 |
+
)}
|
| 231 |
+
<text
|
| 232 |
+
x={node.x} y={node.y + r + 16}
|
| 233 |
+
textAnchor="middle"
|
| 234 |
+
fontSize={isSelected ? "12" : "10.5"}
|
| 235 |
+
fontWeight={isSelected ? "600" : "400"}
|
| 236 |
+
fill="#141413"
|
| 237 |
+
fontFamily="var(--font-sans)"
|
| 238 |
+
>
|
| 239 |
+
{node.label}
|
| 240 |
+
</text>
|
| 241 |
+
</g>
|
| 242 |
+
);
|
| 243 |
+
})}
|
| 244 |
+
</svg>
|
| 245 |
+
</div>
|
| 246 |
+
</div>
|
| 247 |
+
|
| 248 |
+
{/* Sidebar */}
|
| 249 |
+
<div className="flex flex-col gap-4">
|
| 250 |
+
{/* Node Details */}
|
| 251 |
+
<div className="card-dark animate-fade-in-up delay-100">
|
| 252 |
+
<div className="caption-uppercase mb-3" style={{ color: "#a09d96" }}>
|
| 253 |
+
{selectedInfo ? "Node Details" : "Select a Node"}
|
| 254 |
+
</div>
|
| 255 |
+
{selectedInfo ? (
|
| 256 |
+
<div>
|
| 257 |
+
<div className="title-lg" style={{ color: "#faf9f5" }}>{selectedInfo.label}</div>
|
| 258 |
+
<span className="badge mt-3 inline-block" style={{
|
| 259 |
+
background: TYPE_COLORS[selectedInfo.type] || "#AED6F1",
|
| 260 |
+
color: "white", fontSize: "0.6875rem",
|
| 261 |
+
}}>
|
| 262 |
+
{selectedInfo.type}
|
| 263 |
+
</span>
|
| 264 |
+
{selectedInfo.description && (
|
| 265 |
+
<p className="body-sm mt-3" style={{ color: "#a09d96", lineHeight: 1.6 }}>
|
| 266 |
+
{selectedInfo.description}
|
| 267 |
+
</p>
|
| 268 |
+
)}
|
| 269 |
+
<div className="mt-4 pt-4" style={{ borderTop: "1px solid rgba(255,255,255,0.08)" }}>
|
| 270 |
+
<div className="caption mb-2" style={{ color: "#a09d96" }}>
|
| 271 |
+
{connectedEdges.length} Connection{connectedEdges.length !== 1 ? "s" : ""}
|
| 272 |
+
</div>
|
| 273 |
+
{connectedEdges.map((e, i) => {
|
| 274 |
+
const other = e.source === selectedInfo.id ? e.target : e.source;
|
| 275 |
+
const otherNode = nodeMap[other];
|
| 276 |
+
return (
|
| 277 |
+
<div key={i} className="flex items-center gap-2 mb-2 cursor-pointer"
|
| 278 |
+
onClick={() => setSelectedNode(other)}
|
| 279 |
+
style={{ padding: "6px 10px", borderRadius: "8px", background: "rgba(255,255,255,0.04)" }}>
|
| 280 |
+
<div className="w-2.5 h-2.5 rounded-full" style={{
|
| 281 |
+
background: TYPE_COLORS[otherNode?.type ?? ""] || "#AED6F1",
|
| 282 |
+
}} />
|
| 283 |
+
<span style={{ color: "#faf9f5", fontFamily: "var(--font-mono)", fontSize: "0.75rem" }}>
|
| 284 |
+
{e.label}
|
| 285 |
+
</span>
|
| 286 |
+
<span style={{ color: "#a09d96", fontSize: "0.75rem" }}>→</span>
|
| 287 |
+
<span style={{ color: "#faf9f5", fontSize: "0.8125rem" }}>
|
| 288 |
+
{otherNode?.label}
|
| 289 |
+
</span>
|
| 290 |
+
</div>
|
| 291 |
+
);
|
| 292 |
+
})}
|
| 293 |
+
</div>
|
| 294 |
+
</div>
|
| 295 |
+
) : (
|
| 296 |
+
<p className="body-sm" style={{ color: "#a09d96" }}>
|
| 297 |
+
Click any node on the graph to see its details, connections, and type information.
|
| 298 |
+
</p>
|
| 299 |
+
)}
|
| 300 |
+
</div>
|
| 301 |
+
|
| 302 |
+
{/* Graph Stats */}
|
| 303 |
+
<div className="card-cream animate-fade-in-up delay-200">
|
| 304 |
+
<div className="caption-uppercase mb-3">Graph Statistics</div>
|
| 305 |
+
{[
|
| 306 |
+
{ label: "Nodes", value: nodes.length, color: "#FF6B00" },
|
| 307 |
+
{ label: "Edges", value: edges.length, color: "#0072CE" },
|
| 308 |
+
{ label: "Avg Degree", value: (edges.length * 2 / nodes.length).toFixed(1), color: "#5db8a6" },
|
| 309 |
+
{ label: "Entity Types", value: new Set(nodes.map(n => n.type)).size, color: "#cc785c" },
|
| 310 |
+
{ label: "Hops", value: hops, color: "#002B49" },
|
| 311 |
+
].map((s, i) => (
|
| 312 |
+
<div key={i} className="flex justify-between items-center py-2.5"
|
| 313 |
+
style={{ borderBottom: i < 4 ? "1px solid var(--color-hairline-soft)" : "none" }}>
|
| 314 |
+
<span className="body-sm">{s.label}</span>
|
| 315 |
+
<span className="title-sm" style={{ fontFamily: "var(--font-mono)", color: s.color }}>{s.value}</span>
|
| 316 |
+
</div>
|
| 317 |
+
))}
|
| 318 |
+
</div>
|
| 319 |
+
|
| 320 |
+
{/* Legend */}
|
| 321 |
+
<div className="card animate-fade-in-up delay-300" style={{ padding: "20px" }}>
|
| 322 |
+
<div className="caption-uppercase mb-3">Entity Types</div>
|
| 323 |
+
<div className="grid grid-cols-2 gap-2">
|
| 324 |
+
{Object.entries(TYPE_COLORS).map(([type, color]) => (
|
| 325 |
+
<div key={type} className="flex items-center gap-2">
|
| 326 |
+
<div className="w-3 h-3 rounded-full" style={{ background: color }} />
|
| 327 |
+
<span className="body-sm">{type}</span>
|
| 328 |
+
</div>
|
| 329 |
+
))}
|
| 330 |
+
</div>
|
| 331 |
+
</div>
|
| 332 |
+
</div>
|
| 333 |
+
</div>
|
| 334 |
+
|
| 335 |
+
{/* Reasoning Steps */}
|
| 336 |
+
<div className="card mt-8 animate-fade-in-up delay-400">
|
| 337 |
+
<div className="title-lg mb-6">🧠 Graph Reasoning Path</div>
|
| 338 |
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
| 339 |
+
{reasoning.map((step, i) => (
|
| 340 |
+
<div key={i} className="card-cream" style={{ padding: "20px", position: "relative" }}>
|
| 341 |
+
<div style={{
|
| 342 |
+
position: "absolute", top: "12px", right: "12px",
|
| 343 |
+
fontFamily: "var(--font-mono)", fontSize: "0.6875rem",
|
| 344 |
+
color: "var(--color-tiger-orange)", fontWeight: 600,
|
| 345 |
+
}}>
|
| 346 |
+
Step {i + 1}
|
| 347 |
+
</div>
|
| 348 |
+
<p className="body-sm" style={{ color: "var(--color-body)", lineHeight: 1.6 }}>
|
| 349 |
+
{step}
|
| 350 |
+
</p>
|
| 351 |
+
</div>
|
| 352 |
+
))}
|
| 353 |
+
</div>
|
| 354 |
+
</div>
|
| 355 |
+
</div>
|
| 356 |
+
);
|
| 357 |
+
}
|
web/src/components/home/BentoShowcase.tsx
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import Link from "next/link";
|
| 4 |
+
|
| 5 |
+
export function BentoShowcase() {
|
| 6 |
+
return (
|
| 7 |
+
<section className="section">
|
| 8 |
+
<div className="container">
|
| 9 |
+
<div className="text-center mb-16">
|
| 10 |
+
<div className="caption-uppercase mb-3" style={{ color: "var(--color-tiger-orange)" }}>
|
| 11 |
+
The Dashboard
|
| 12 |
+
</div>
|
| 13 |
+
<h2 className="display-lg mb-4">Everything in one place</h2>
|
| 14 |
+
<p className="body-lg mx-auto" style={{ maxWidth: "520px", color: "var(--color-muted)" }}>
|
| 15 |
+
Six dedicated pages for every aspect of GraphRAG — from live comparisons to deep graph exploration.
|
| 16 |
+
</p>
|
| 17 |
+
</div>
|
| 18 |
+
|
| 19 |
+
<div className="bento-grid bento-grid-3">
|
| 20 |
+
{/* Large: Playground */}
|
| 21 |
+
<Link href="/playground" className="no-underline bento-span-2">
|
| 22 |
+
<div className="card-gradient-orange card-hover" style={{ padding: "40px", minHeight: "240px", cursor: "pointer" }}>
|
| 23 |
+
<div style={{ fontSize: "2rem", marginBottom: "12px" }}>⚡</div>
|
| 24 |
+
<h3 style={{ fontSize: "1.5rem", fontFamily: "var(--font-serif)", fontWeight: 400, color: "white", marginBottom: "8px" }}>
|
| 25 |
+
Live Playground
|
| 26 |
+
</h3>
|
| 27 |
+
<p style={{ color: "rgba(255,255,255,0.8)", fontSize: "0.9375rem", maxWidth: "400px" }}>
|
| 28 |
+
Ask any question and watch both pipelines race side-by-side.
|
| 29 |
+
Real-time token counting, cost tracking, and quality metrics.
|
| 30 |
+
</p>
|
| 31 |
+
<div className="flex gap-2 mt-4">
|
| 32 |
+
<span className="badge" style={{ background: "rgba(255,255,255,0.2)", color: "white", fontSize: "0.6875rem" }}>12 LLM Providers</span>
|
| 33 |
+
<span className="badge" style={{ background: "rgba(255,255,255,0.2)", color: "white", fontSize: "0.6875rem" }}>Adaptive Routing</span>
|
| 34 |
+
</div>
|
| 35 |
+
</div>
|
| 36 |
+
</Link>
|
| 37 |
+
|
| 38 |
+
{/* Small: Benchmarks */}
|
| 39 |
+
<Link href="/benchmarks" className="no-underline">
|
| 40 |
+
<div className="card-dark card-hover" style={{ padding: "32px", minHeight: "240px", cursor: "pointer" }}>
|
| 41 |
+
<div style={{ fontSize: "2rem", marginBottom: "12px" }}>📊</div>
|
| 42 |
+
<h3 className="title-lg" style={{ color: "var(--color-on-dark)", marginBottom: "8px" }}>
|
| 43 |
+
Benchmarks
|
| 44 |
+
</h3>
|
| 45 |
+
<p className="body-sm" style={{ color: "var(--color-on-dark-soft)" }}>
|
| 46 |
+
Run HotpotQA benchmarks with F1, EM, and radar charts.
|
| 47 |
+
</p>
|
| 48 |
+
</div>
|
| 49 |
+
</Link>
|
| 50 |
+
|
| 51 |
+
{/* Small: Graph Explorer */}
|
| 52 |
+
<Link href="/explorer" className="no-underline">
|
| 53 |
+
<div className="card card-hover" style={{ padding: "32px", minHeight: "220px", cursor: "pointer", borderColor: "#5db8a6" }}>
|
| 54 |
+
<div style={{ fontSize: "2rem", marginBottom: "12px" }}>🕸️</div>
|
| 55 |
+
<h3 className="title-lg" style={{ marginBottom: "8px" }}>
|
| 56 |
+
Graph Explorer
|
| 57 |
+
</h3>
|
| 58 |
+
<p className="body-sm" style={{ color: "var(--color-muted)" }}>
|
| 59 |
+
Interactive knowledge graph visualization with entity inspection.
|
| 60 |
+
</p>
|
| 61 |
+
</div>
|
| 62 |
+
</Link>
|
| 63 |
+
|
| 64 |
+
{/* Medium: Architecture */}
|
| 65 |
+
<Link href="/architecture" className="no-underline bento-span-2">
|
| 66 |
+
<div className="card-gradient-blue card-hover" style={{ padding: "40px", minHeight: "220px", cursor: "pointer" }}>
|
| 67 |
+
<div style={{ fontSize: "2rem", marginBottom: "12px" }}>🏗️</div>
|
| 68 |
+
<h3 style={{ fontSize: "1.375rem", fontFamily: "var(--font-serif)", fontWeight: 400, color: "white", marginBottom: "8px" }}>
|
| 69 |
+
Architecture Deep-Dive
|
| 70 |
+
</h3>
|
| 71 |
+
<p style={{ color: "rgba(255,255,255,0.75)", fontSize: "0.9375rem", maxWidth: "480px" }}>
|
| 72 |
+
Explore the 4-layer AI Factory model, GSQL queries, and how TigerGraph integrates with the LLM pipeline.
|
| 73 |
+
</p>
|
| 74 |
+
<div className="flex gap-2 mt-4">
|
| 75 |
+
<span className="badge" style={{ background: "rgba(255,255,255,0.15)", color: "white", fontSize: "0.6875rem" }}>4 Layers</span>
|
| 76 |
+
<span className="badge" style={{ background: "rgba(255,255,255,0.15)", color: "white", fontSize: "0.6875rem" }}>GSQL Queries</span>
|
| 77 |
+
<span className="badge" style={{ background: "rgba(255,255,255,0.15)", color: "white", fontSize: "0.6875rem" }}>Docker Ready</span>
|
| 78 |
+
</div>
|
| 79 |
+
</div>
|
| 80 |
+
</Link>
|
| 81 |
+
</div>
|
| 82 |
+
</div>
|
| 83 |
+
</section>
|
| 84 |
+
);
|
| 85 |
+
}
|
web/src/components/home/CTASection.tsx
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import Link from "next/link";
|
| 4 |
+
|
| 5 |
+
export function CTASection() {
|
| 6 |
+
return (
|
| 7 |
+
<section style={{
|
| 8 |
+
background: "linear-gradient(135deg, #002B49 0%, #003D6B 50%, #002B49 100%)",
|
| 9 |
+
padding: "96px 0",
|
| 10 |
+
position: "relative",
|
| 11 |
+
overflow: "hidden",
|
| 12 |
+
}}>
|
| 13 |
+
{/* Decorative circles */}
|
| 14 |
+
<div style={{
|
| 15 |
+
position: "absolute", top: "-100px", right: "-100px",
|
| 16 |
+
width: "400px", height: "400px", borderRadius: "50%",
|
| 17 |
+
border: "1px solid rgba(255,107,0,0.1)",
|
| 18 |
+
}} />
|
| 19 |
+
<div style={{
|
| 20 |
+
position: "absolute", bottom: "-60px", left: "-60px",
|
| 21 |
+
width: "300px", height: "300px", borderRadius: "50%",
|
| 22 |
+
border: "1px solid rgba(255,107,0,0.08)",
|
| 23 |
+
}} />
|
| 24 |
+
|
| 25 |
+
<div className="container text-center" style={{ position: "relative" }}>
|
| 26 |
+
<div className="badge mb-6" style={{ background: "rgba(255,107,0,0.15)", color: "#FF6B00", fontSize: "0.75rem" }}>
|
| 27 |
+
Ready to explore?
|
| 28 |
+
</div>
|
| 29 |
+
<h2 style={{
|
| 30 |
+
fontFamily: "var(--font-serif)",
|
| 31 |
+
fontSize: "clamp(2rem, 4vw, 3.5rem)",
|
| 32 |
+
fontWeight: 400,
|
| 33 |
+
color: "white",
|
| 34 |
+
letterSpacing: "-1px",
|
| 35 |
+
lineHeight: 1.1,
|
| 36 |
+
marginBottom: "24px",
|
| 37 |
+
}}>
|
| 38 |
+
See the difference<br />graphs make
|
| 39 |
+
</h2>
|
| 40 |
+
<p style={{
|
| 41 |
+
fontSize: "1.125rem", color: "rgba(255,255,255,0.6)",
|
| 42 |
+
maxWidth: "480px", margin: "0 auto 40px",
|
| 43 |
+
lineHeight: 1.6,
|
| 44 |
+
}}>
|
| 45 |
+
Run a live comparison between Baseline RAG and GraphRAG.
|
| 46 |
+
Measure F1, tokens, cost, and latency — all in your browser.
|
| 47 |
+
</p>
|
| 48 |
+
<div className="flex flex-wrap gap-4 justify-center">
|
| 49 |
+
<Link href="/playground" className="btn btn-primary btn-lg no-underline">
|
| 50 |
+
Launch Playground →
|
| 51 |
+
</Link>
|
| 52 |
+
<a
|
| 53 |
+
href="https://github.com/MUTHUKUMARAN-K-1/graphrag-inference-hackathon"
|
| 54 |
+
target="_blank"
|
| 55 |
+
rel="noopener noreferrer"
|
| 56 |
+
className="btn btn-on-dark btn-lg no-underline"
|
| 57 |
+
>
|
| 58 |
+
⭐ Star on GitHub
|
| 59 |
+
</a>
|
| 60 |
+
</div>
|
| 61 |
+
</div>
|
| 62 |
+
</section>
|
| 63 |
+
);
|
| 64 |
+
}
|
web/src/components/home/FeaturesSection.tsx
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
const FEATURES = [
|
| 4 |
+
{
|
| 5 |
+
icon: "🧠",
|
| 6 |
+
iconClass: "feature-icon-orange",
|
| 7 |
+
title: "Adaptive Query Router",
|
| 8 |
+
description: "Analyzes query complexity in real-time and routes simple questions to fast baseline RAG while directing multi-hop questions through the knowledge graph.",
|
| 9 |
+
tag: "Novel",
|
| 10 |
+
tagColor: "#FF6B00",
|
| 11 |
+
},
|
| 12 |
+
{
|
| 13 |
+
icon: "📋",
|
| 14 |
+
iconClass: "feature-icon-blue",
|
| 15 |
+
title: "Schema-Bounded Extraction",
|
| 16 |
+
description: "Constrains entity extraction to TigerGraph's schema, eliminating hallucinated node types and ensuring every extracted entity maps to a valid graph vertex.",
|
| 17 |
+
tag: "Novel",
|
| 18 |
+
tagColor: "#FF6B00",
|
| 19 |
+
},
|
| 20 |
+
{
|
| 21 |
+
icon: "🔑",
|
| 22 |
+
iconClass: "feature-icon-teal",
|
| 23 |
+
title: "Dual-Level Keywords",
|
| 24 |
+
description: "Extracts both high-level concepts and low-level entities from queries, enabling graph traversal at multiple granularity levels for richer context.",
|
| 25 |
+
tag: "Novel",
|
| 26 |
+
tagColor: "#FF6B00",
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
icon: "🔗",
|
| 30 |
+
iconClass: "feature-icon-amber",
|
| 31 |
+
title: "Graph Reasoning Paths",
|
| 32 |
+
description: "Traces explicit entity→relation→entity chains through TigerGraph, providing human-readable evidence paths that make LLM answers verifiable.",
|
| 33 |
+
tag: "Novel",
|
| 34 |
+
tagColor: "#FF6B00",
|
| 35 |
+
},
|
| 36 |
+
{
|
| 37 |
+
icon: "📊",
|
| 38 |
+
iconClass: "feature-icon-coral",
|
| 39 |
+
title: "Real-Time Cost Tracking",
|
| 40 |
+
description: "Measures tokens, latency, and USD cost per query for both pipelines simultaneously, with interactive projections at scale.",
|
| 41 |
+
tag: "Novel",
|
| 42 |
+
tagColor: "#FF6B00",
|
| 43 |
+
},
|
| 44 |
+
{
|
| 45 |
+
icon: "🌐",
|
| 46 |
+
iconClass: "feature-icon-blue",
|
| 47 |
+
title: "12 LLM Providers",
|
| 48 |
+
description: "Universal LLM layer supporting Claude, GPT-4, Gemini, Llama, Mistral, DeepSeek, Grok, Cohere, and more — swap providers with one parameter.",
|
| 49 |
+
tag: "Universal",
|
| 50 |
+
tagColor: "#0072CE",
|
| 51 |
+
},
|
| 52 |
+
];
|
| 53 |
+
|
| 54 |
+
export function FeaturesSection() {
|
| 55 |
+
return (
|
| 56 |
+
<section className="section" style={{ background: "var(--color-canvas)" }}>
|
| 57 |
+
<div className="container">
|
| 58 |
+
{/* Header */}
|
| 59 |
+
<div className="text-center mb-16">
|
| 60 |
+
<div className="caption-uppercase mb-3" style={{ color: "var(--color-tiger-orange)" }}>
|
| 61 |
+
What Makes It Different
|
| 62 |
+
</div>
|
| 63 |
+
<h2 className="display-lg mb-4">Five novel features,<br />one unified system</h2>
|
| 64 |
+
<p className="body-lg mx-auto" style={{ maxWidth: "560px", color: "var(--color-muted)" }}>
|
| 65 |
+
Each feature was designed to solve a specific GraphRAG challenge —
|
| 66 |
+
from query routing to cost optimization.
|
| 67 |
+
</p>
|
| 68 |
+
</div>
|
| 69 |
+
|
| 70 |
+
{/* Feature Grid */}
|
| 71 |
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
| 72 |
+
{FEATURES.map((feature, i) => (
|
| 73 |
+
<div
|
| 74 |
+
key={i}
|
| 75 |
+
className="card card-hover"
|
| 76 |
+
style={{ animationDelay: `${i * 0.1}s` }}
|
| 77 |
+
>
|
| 78 |
+
<div className={`feature-icon ${feature.iconClass}`}>
|
| 79 |
+
{feature.icon}
|
| 80 |
+
</div>
|
| 81 |
+
<div className="flex items-center gap-2 mb-3">
|
| 82 |
+
<h3 className="title-md">{feature.title}</h3>
|
| 83 |
+
<span className="badge" style={{
|
| 84 |
+
background: `${feature.tagColor}15`,
|
| 85 |
+
color: feature.tagColor,
|
| 86 |
+
fontSize: "0.6875rem",
|
| 87 |
+
padding: "2px 8px",
|
| 88 |
+
}}>
|
| 89 |
+
{feature.tag}
|
| 90 |
+
</span>
|
| 91 |
+
</div>
|
| 92 |
+
<p className="body-sm" style={{ color: "var(--color-muted)" }}>
|
| 93 |
+
{feature.description}
|
| 94 |
+
</p>
|
| 95 |
+
</div>
|
| 96 |
+
))}
|
| 97 |
+
</div>
|
| 98 |
+
</div>
|
| 99 |
+
</section>
|
| 100 |
+
);
|
| 101 |
+
}
|
web/src/components/home/HowItWorks.tsx
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
const STEPS = [
|
| 4 |
+
{
|
| 5 |
+
number: "01",
|
| 6 |
+
title: "Query Enters the System",
|
| 7 |
+
description: "A natural language question arrives. The Adaptive Router analyzes complexity, entity count, and multi-hop indicators to classify the query.",
|
| 8 |
+
detail: "Complexity score: 0.0–1.0 | Types: bridge, comparison, factoid",
|
| 9 |
+
color: "#FF6B00",
|
| 10 |
+
},
|
| 11 |
+
{
|
| 12 |
+
number: "02",
|
| 13 |
+
title: "Dual Pipeline Activation",
|
| 14 |
+
description: "Both pipelines execute simultaneously: Baseline RAG (vector search → LLM) and GraphRAG (entity extraction → graph traversal → LLM).",
|
| 15 |
+
detail: "Schema-bounded extraction ensures valid entities | GSQL multi-hop traversal",
|
| 16 |
+
color: "#0072CE",
|
| 17 |
+
},
|
| 18 |
+
{
|
| 19 |
+
number: "03",
|
| 20 |
+
title: "Graph Traversal & Evidence",
|
| 21 |
+
description: "TigerGraph traces entity→relation→entity paths through the knowledge graph, collecting structured evidence that the LLM can follow.",
|
| 22 |
+
detail: "2-hop traversal | Reasoning paths | Dual-level keywords",
|
| 23 |
+
color: "#5db8a6",
|
| 24 |
+
},
|
| 25 |
+
{
|
| 26 |
+
number: "04",
|
| 27 |
+
title: "LLM Generation & Evaluation",
|
| 28 |
+
description: "Claude (or any of 12 providers) generates answers from the structured context. RAGAS evaluates F1, Exact Match, and quality metrics in real-time.",
|
| 29 |
+
detail: "Cost tracking | Token counting | Latency measurement",
|
| 30 |
+
color: "#cc785c",
|
| 31 |
+
},
|
| 32 |
+
];
|
| 33 |
+
|
| 34 |
+
export function HowItWorks() {
|
| 35 |
+
return (
|
| 36 |
+
<section className="section" style={{ background: "var(--color-surface-soft)" }}>
|
| 37 |
+
<div className="container">
|
| 38 |
+
<div className="text-center mb-16">
|
| 39 |
+
<div className="caption-uppercase mb-3" style={{ color: "var(--color-tiger-orange)" }}>
|
| 40 |
+
How It Works
|
| 41 |
+
</div>
|
| 42 |
+
<h2 className="display-lg mb-4">From query to answer<br />in four steps</h2>
|
| 43 |
+
</div>
|
| 44 |
+
|
| 45 |
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
| 46 |
+
{STEPS.map((step, i) => (
|
| 47 |
+
<div key={i} className="card card-hover" style={{ position: "relative", overflow: "hidden" }}>
|
| 48 |
+
{/* Step Number */}
|
| 49 |
+
<div style={{
|
| 50 |
+
position: "absolute", top: "-12px", right: "-8px",
|
| 51 |
+
fontFamily: "var(--font-serif)",
|
| 52 |
+
fontSize: "6rem",
|
| 53 |
+
fontWeight: 700,
|
| 54 |
+
color: step.color,
|
| 55 |
+
opacity: 0.06,
|
| 56 |
+
lineHeight: 1,
|
| 57 |
+
}}>
|
| 58 |
+
{step.number}
|
| 59 |
+
</div>
|
| 60 |
+
|
| 61 |
+
<div className="flex gap-4">
|
| 62 |
+
<div style={{
|
| 63 |
+
width: "48px", height: "48px", borderRadius: "12px",
|
| 64 |
+
background: `${step.color}12`,
|
| 65 |
+
display: "flex", alignItems: "center", justifyContent: "center",
|
| 66 |
+
fontFamily: "var(--font-mono)",
|
| 67 |
+
fontWeight: 700, fontSize: "0.875rem",
|
| 68 |
+
color: step.color,
|
| 69 |
+
flexShrink: 0,
|
| 70 |
+
}}>
|
| 71 |
+
{step.number}
|
| 72 |
+
</div>
|
| 73 |
+
<div>
|
| 74 |
+
<h3 className="title-md mb-2">{step.title}</h3>
|
| 75 |
+
<p className="body-sm mb-3" style={{ color: "var(--color-muted)" }}>
|
| 76 |
+
{step.description}
|
| 77 |
+
</p>
|
| 78 |
+
<div style={{
|
| 79 |
+
padding: "8px 12px",
|
| 80 |
+
background: "var(--color-surface-soft)",
|
| 81 |
+
borderRadius: "8px",
|
| 82 |
+
fontFamily: "var(--font-mono)",
|
| 83 |
+
fontSize: "0.75rem",
|
| 84 |
+
color: "var(--color-muted)",
|
| 85 |
+
}}>
|
| 86 |
+
{step.detail}
|
| 87 |
+
</div>
|
| 88 |
+
</div>
|
| 89 |
+
</div>
|
| 90 |
+
</div>
|
| 91 |
+
))}
|
| 92 |
+
</div>
|
| 93 |
+
</div>
|
| 94 |
+
</section>
|
| 95 |
+
);
|
| 96 |
+
}
|
web/src/components/home/TestimonialsSection.tsx
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
const TESTIMONIALS = [
|
| 4 |
+
{
|
| 5 |
+
quote: "GraphRAG reduces token usage by routing simple queries to baseline while giving complex multi-hop questions the structured reasoning they need.",
|
| 6 |
+
label: "Adaptive Intelligence",
|
| 7 |
+
icon: "🧠",
|
| 8 |
+
metric: "40% fewer tokens on simple queries",
|
| 9 |
+
bg: "var(--color-canvas)",
|
| 10 |
+
},
|
| 11 |
+
{
|
| 12 |
+
quote: "Schema-bounded extraction ensures every entity maps to a valid TigerGraph vertex — no hallucinated node types, no broken traversals.",
|
| 13 |
+
label: "Graph Integrity",
|
| 14 |
+
icon: "📋",
|
| 15 |
+
metric: "Zero invalid entity types",
|
| 16 |
+
bg: "var(--color-surface-card)",
|
| 17 |
+
},
|
| 18 |
+
{
|
| 19 |
+
quote: "The reasoning path visualization shows exactly which graph edges were traversed, making every LLM answer traceable and verifiable.",
|
| 20 |
+
label: "Explainability",
|
| 21 |
+
icon: "🔗",
|
| 22 |
+
metric: "Full evidence chain per answer",
|
| 23 |
+
bg: "var(--color-canvas)",
|
| 24 |
+
},
|
| 25 |
+
];
|
| 26 |
+
|
| 27 |
+
export function TestimonialsSection() {
|
| 28 |
+
return (
|
| 29 |
+
<section className="section" style={{ background: "var(--color-surface-soft)" }}>
|
| 30 |
+
<div className="container">
|
| 31 |
+
<div className="text-center mb-12">
|
| 32 |
+
<div className="caption-uppercase mb-3" style={{ color: "var(--color-tiger-orange)" }}>
|
| 33 |
+
Key Insights
|
| 34 |
+
</div>
|
| 35 |
+
<h2 className="display-lg">Why graphs change the game</h2>
|
| 36 |
+
</div>
|
| 37 |
+
|
| 38 |
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
| 39 |
+
{TESTIMONIALS.map((t, i) => (
|
| 40 |
+
<div
|
| 41 |
+
key={i}
|
| 42 |
+
className="card card-hover"
|
| 43 |
+
style={{ background: t.bg }}
|
| 44 |
+
>
|
| 45 |
+
<div style={{ fontSize: "2rem", marginBottom: "16px" }}>{t.icon}</div>
|
| 46 |
+
<div className="caption-uppercase mb-2" style={{ color: "var(--color-tiger-orange)" }}>
|
| 47 |
+
{t.label}
|
| 48 |
+
</div>
|
| 49 |
+
<p className="body-md mb-4" style={{ color: "var(--color-body)" }}>
|
| 50 |
+
“{t.quote}”
|
| 51 |
+
</p>
|
| 52 |
+
<div style={{
|
| 53 |
+
padding: "8px 14px",
|
| 54 |
+
background: "var(--color-tiger-orange-light)",
|
| 55 |
+
borderRadius: "8px",
|
| 56 |
+
fontFamily: "var(--font-mono)",
|
| 57 |
+
fontSize: "0.8125rem",
|
| 58 |
+
fontWeight: 500,
|
| 59 |
+
color: "var(--color-tiger-orange)",
|
| 60 |
+
}}>
|
| 61 |
+
{t.metric}
|
| 62 |
+
</div>
|
| 63 |
+
</div>
|
| 64 |
+
))}
|
| 65 |
+
</div>
|
| 66 |
+
</div>
|
| 67 |
+
</section>
|
| 68 |
+
);
|
| 69 |
+
}
|
web/src/components/playground/PlaygroundContent.tsx
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { useState, useEffect } from "react";
|
| 4 |
+
import {
|
| 5 |
+
BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip,
|
| 6 |
+
ResponsiveContainer, Legend,
|
| 7 |
+
} from "recharts";
|
| 8 |
+
|
| 9 |
+
interface PipelineResult {
|
| 10 |
+
answer: string;
|
| 11 |
+
tokens: number;
|
| 12 |
+
latencyMs: number;
|
| 13 |
+
costUsd: number;
|
| 14 |
+
entities: string[];
|
| 15 |
+
relations: string[];
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
interface ProviderInfo {
|
| 19 |
+
id: string;
|
| 20 |
+
name: string;
|
| 21 |
+
isLocal: boolean;
|
| 22 |
+
hasApiKey: boolean;
|
| 23 |
+
defaultModel: string;
|
| 24 |
+
models: { id: string; name: string; speed: string; quality: string }[];
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
const EXAMPLES = [
|
| 28 |
+
{ q: "Were Scott Derrickson and Ed Wood of the same nationality?", type: "Bridge" },
|
| 29 |
+
{ q: "What government position was held by the woman who portrayed Nora Batty?", type: "Multi-hop" },
|
| 30 |
+
{ q: "Which magazine was started first, Arthur's Magazine or First for Women?", type: "Comparison" },
|
| 31 |
+
{ q: "Who was born first, Arthur Conan Doyle or Agatha Christie?", type: "Comparison" },
|
| 32 |
+
{ q: "What is the capital of the country where the Eiffel Tower is located?", type: "Bridge" },
|
| 33 |
+
];
|
| 34 |
+
|
| 35 |
+
const FALLBACK_PROVIDERS: ProviderInfo[] = [
|
| 36 |
+
{ id: "anthropic", name: "Anthropic Claude", isLocal: false, hasApiKey: false, defaultModel: "claude-sonnet-4-20250514", models: [{ id: "claude-sonnet-4-20250514", name: "Claude Sonnet 4", speed: "medium", quality: "high" }] },
|
| 37 |
+
{ id: "openai", name: "OpenAI", isLocal: false, hasApiKey: false, defaultModel: "gpt-4o-mini", models: [{ id: "gpt-4o-mini", name: "GPT-4o Mini", speed: "fast", quality: "medium" }] },
|
| 38 |
+
{ id: "gemini", name: "Google Gemini", isLocal: false, hasApiKey: false, defaultModel: "gemini-2.0-flash", models: [{ id: "gemini-2.0-flash", name: "Gemini 2.0 Flash", speed: "fast", quality: "medium" }] },
|
| 39 |
+
{ id: "groq", name: "Groq", isLocal: false, hasApiKey: false, defaultModel: "llama-3.3-70b-versatile", models: [{ id: "llama-3.3-70b-versatile", name: "Llama 3.3 70B", speed: "fast", quality: "high" }] },
|
| 40 |
+
{ id: "mistral", name: "Mistral AI", isLocal: false, hasApiKey: false, defaultModel: "mistral-large-latest", models: [{ id: "mistral-large-latest", name: "Mistral Large", speed: "medium", quality: "high" }] },
|
| 41 |
+
{ id: "deepseek", name: "DeepSeek", isLocal: false, hasApiKey: false, defaultModel: "deepseek-chat", models: [{ id: "deepseek-chat", name: "DeepSeek V3", speed: "fast", quality: "high" }] },
|
| 42 |
+
];
|
| 43 |
+
|
| 44 |
+
export function PlaygroundContent() {
|
| 45 |
+
const [providers, setProviders] = useState<ProviderInfo[]>(FALLBACK_PROVIDERS);
|
| 46 |
+
const [loading, setLoading] = useState(false);
|
| 47 |
+
const [query, setQuery] = useState("");
|
| 48 |
+
const [provider, setProvider] = useState("anthropic");
|
| 49 |
+
const [model, setModel] = useState("");
|
| 50 |
+
const [adaptiveRouting, setAdaptiveRouting] = useState(true);
|
| 51 |
+
const [baseline, setBaseline] = useState<PipelineResult | null>(null);
|
| 52 |
+
const [graphrag, setGraphrag] = useState<PipelineResult | null>(null);
|
| 53 |
+
const [complexity, setComplexity] = useState(0);
|
| 54 |
+
const [queryType, setQueryType] = useState("");
|
| 55 |
+
const [recommended, setRecommended] = useState("");
|
| 56 |
+
const [demoMode, setDemoMode] = useState(false);
|
| 57 |
+
|
| 58 |
+
useEffect(() => {
|
| 59 |
+
fetch("/api/providers").then(r => r.json()).then(d => {
|
| 60 |
+
if (d.providers) setProviders(d.providers);
|
| 61 |
+
}).catch(() => {});
|
| 62 |
+
}, []);
|
| 63 |
+
|
| 64 |
+
const selectedProvider = providers.find(p => p.id === provider) || providers[0];
|
| 65 |
+
const selectedModel = model || selectedProvider?.defaultModel || "";
|
| 66 |
+
|
| 67 |
+
const runComparison = async () => {
|
| 68 |
+
if (!query.trim()) return;
|
| 69 |
+
setLoading(true);
|
| 70 |
+
try {
|
| 71 |
+
const res = await fetch("/api/compare", {
|
| 72 |
+
method: "POST",
|
| 73 |
+
headers: { "Content-Type": "application/json" },
|
| 74 |
+
body: JSON.stringify({ query, adaptiveRouting, provider, model: selectedModel }),
|
| 75 |
+
});
|
| 76 |
+
const data = await res.json();
|
| 77 |
+
setBaseline(data.baseline);
|
| 78 |
+
setGraphrag(data.graphrag);
|
| 79 |
+
setComplexity(data.complexity ?? 0);
|
| 80 |
+
setQueryType(data.queryType ?? "");
|
| 81 |
+
setRecommended(data.recommended ?? "");
|
| 82 |
+
setDemoMode(data.demoMode ?? false);
|
| 83 |
+
} catch {
|
| 84 |
+
// silently fail
|
| 85 |
+
}
|
| 86 |
+
setLoading(false);
|
| 87 |
+
};
|
| 88 |
+
|
| 89 |
+
const chartData = baseline && graphrag ? [
|
| 90 |
+
{ name: "Tokens", Baseline: baseline.tokens, GraphRAG: graphrag.tokens },
|
| 91 |
+
{ name: "Latency (ms)", Baseline: Math.round(baseline.latencyMs), GraphRAG: Math.round(graphrag.latencyMs) },
|
| 92 |
+
] : [];
|
| 93 |
+
|
| 94 |
+
return (
|
| 95 |
+
<div>
|
| 96 |
+
{/* Provider Selection */}
|
| 97 |
+
<div className="card mb-6 animate-fade-in-up">
|
| 98 |
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
| 99 |
+
<div>
|
| 100 |
+
<label className="caption mb-2 block">LLM Provider</label>
|
| 101 |
+
<select className="input" value={provider} onChange={e => { setProvider(e.target.value); setModel(""); }}>
|
| 102 |
+
{providers.map(p => (
|
| 103 |
+
<option key={p.id} value={p.id}>
|
| 104 |
+
{p.isLocal ? "🦙 " : "☁️ "}{p.name}
|
| 105 |
+
</option>
|
| 106 |
+
))}
|
| 107 |
+
</select>
|
| 108 |
+
</div>
|
| 109 |
+
<div>
|
| 110 |
+
<label className="caption mb-2 block">Model</label>
|
| 111 |
+
<select className="input" value={selectedModel} onChange={e => setModel(e.target.value)}>
|
| 112 |
+
{selectedProvider?.models.map(m => (
|
| 113 |
+
<option key={m.id} value={m.id}>{m.name} ({m.speed}/{m.quality})</option>
|
| 114 |
+
))}
|
| 115 |
+
</select>
|
| 116 |
+
</div>
|
| 117 |
+
<div className="flex flex-col justify-end gap-2">
|
| 118 |
+
<label className="flex items-center gap-2 caption cursor-pointer">
|
| 119 |
+
<input type="checkbox" checked={adaptiveRouting}
|
| 120 |
+
onChange={e => setAdaptiveRouting(e.target.checked)}
|
| 121 |
+
className="w-4 h-4 accent-[#FF6B00] rounded" />
|
| 122 |
+
🧠 Adaptive Routing
|
| 123 |
+
</label>
|
| 124 |
+
{!selectedProvider?.hasApiKey && !selectedProvider?.isLocal && (
|
| 125 |
+
<span className="badge-outline" style={{ fontSize: "0.6875rem", alignSelf: "flex-start" }}>
|
| 126 |
+
Demo Mode — No API Key
|
| 127 |
+
</span>
|
| 128 |
+
)}
|
| 129 |
+
</div>
|
| 130 |
+
</div>
|
| 131 |
+
</div>
|
| 132 |
+
|
| 133 |
+
{/* Query Input */}
|
| 134 |
+
<div className="card mb-6 animate-fade-in-up delay-100">
|
| 135 |
+
<div className="flex flex-col lg:flex-row gap-4">
|
| 136 |
+
<div className="flex-1">
|
| 137 |
+
<textarea
|
| 138 |
+
className="input textarea"
|
| 139 |
+
placeholder="Ask a multi-hop question… e.g., Were Scott Derrickson and Ed Wood of the same nationality?"
|
| 140 |
+
value={query}
|
| 141 |
+
onChange={e => setQuery(e.target.value)}
|
| 142 |
+
onKeyDown={e => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); runComparison(); } }}
|
| 143 |
+
rows={3}
|
| 144 |
+
style={{ fontSize: "1.0625rem" }}
|
| 145 |
+
/>
|
| 146 |
+
</div>
|
| 147 |
+
<div className="lg:w-56 flex flex-col gap-3 justify-end">
|
| 148 |
+
<button className="btn btn-primary btn-lg w-full" onClick={runComparison}
|
| 149 |
+
disabled={loading || !query.trim()}>
|
| 150 |
+
{loading ? (
|
| 151 |
+
<span className="flex items-center gap-2">
|
| 152 |
+
<span className="animate-spin inline-block w-5 h-5 border-2 border-white border-t-transparent rounded-full" />
|
| 153 |
+
Running…
|
| 154 |
+
</span>
|
| 155 |
+
) : (
|
| 156 |
+
<>▶ Compare Pipelines</>
|
| 157 |
+
)}
|
| 158 |
+
</button>
|
| 159 |
+
</div>
|
| 160 |
+
</div>
|
| 161 |
+
|
| 162 |
+
{/* Example Questions */}
|
| 163 |
+
<div className="mt-5 pt-5" style={{ borderTop: "1px solid var(--color-hairline-soft)" }}>
|
| 164 |
+
<span className="caption mr-3">Try these:</span>
|
| 165 |
+
<div className="flex flex-wrap gap-2 mt-2">
|
| 166 |
+
{EXAMPLES.map((ex, i) => (
|
| 167 |
+
<button key={i} className="badge-outline cursor-pointer hover:bg-surface-soft transition-all"
|
| 168 |
+
style={{ fontSize: "0.75rem", padding: "6px 12px" }}
|
| 169 |
+
onClick={() => setQuery(ex.q)}>
|
| 170 |
+
<span style={{ color: "var(--color-tiger-orange)", marginRight: "4px" }}>{ex.type}</span>
|
| 171 |
+
{ex.q.slice(0, 45)}…
|
| 172 |
+
</button>
|
| 173 |
+
))}
|
| 174 |
+
</div>
|
| 175 |
+
</div>
|
| 176 |
+
</div>
|
| 177 |
+
|
| 178 |
+
{/* Demo Mode Banner */}
|
| 179 |
+
{demoMode && baseline && (
|
| 180 |
+
<div className="card-cream mb-6 flex items-center gap-3 animate-fade-in" style={{ padding: "14px 20px" }}>
|
| 181 |
+
<span style={{ fontSize: "1.25rem" }}>ℹ️</span>
|
| 182 |
+
<span className="body-sm">
|
| 183 |
+
<strong>Demo Mode</strong> — Showing sample data. Set your API key for live results.
|
| 184 |
+
</span>
|
| 185 |
+
</div>
|
| 186 |
+
)}
|
| 187 |
+
|
| 188 |
+
{/* Adaptive Routing Info */}
|
| 189 |
+
{recommended && !demoMode && (
|
| 190 |
+
<div className="card mb-6 animate-fade-in" style={{
|
| 191 |
+
padding: "14px 24px",
|
| 192 |
+
borderLeft: `4px solid ${recommended === "graphrag" ? "#FF6B00" : "#0072CE"}`,
|
| 193 |
+
}}>
|
| 194 |
+
<div className="flex items-center gap-3 flex-wrap">
|
| 195 |
+
<span className="badge-glow" style={{ fontSize: "0.6875rem" }}>🧠 Adaptive Router</span>
|
| 196 |
+
<span className="body-sm">
|
| 197 |
+
Complexity: <strong style={{ fontFamily: "var(--font-mono)" }}>{complexity.toFixed(2)}</strong>
|
| 198 |
+
<span style={{ color: "var(--color-muted)", margin: "0 8px" }}>·</span>
|
| 199 |
+
Type: <strong>{queryType}</strong>
|
| 200 |
+
<span style={{ color: "var(--color-muted)", margin: "0 8px" }}>·</span>
|
| 201 |
+
Recommended: <strong style={{ color: recommended === "graphrag" ? "#FF6B00" : "#0072CE" }}>
|
| 202 |
+
{recommended === "graphrag" ? "GraphRAG" : "Baseline RAG"}
|
| 203 |
+
</strong>
|
| 204 |
+
</span>
|
| 205 |
+
</div>
|
| 206 |
+
</div>
|
| 207 |
+
)}
|
| 208 |
+
|
| 209 |
+
{/* Results */}
|
| 210 |
+
{baseline && graphrag && (
|
| 211 |
+
<div className="animate-fade-in-up">
|
| 212 |
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
| 213 |
+
{/* Baseline Card */}
|
| 214 |
+
<div className="card pipeline-baseline" style={{ position: "relative" }}>
|
| 215 |
+
<div style={{
|
| 216 |
+
position: "absolute", top: "16px", right: "16px",
|
| 217 |
+
width: "8px", height: "8px", borderRadius: "50%",
|
| 218 |
+
background: "#0072CE",
|
| 219 |
+
boxShadow: "0 0 8px rgba(0,114,206,0.4)",
|
| 220 |
+
}} />
|
| 221 |
+
<div className="caption-uppercase mb-1" style={{ color: "#0072CE" }}>Pipeline A</div>
|
| 222 |
+
<div className="title-lg mb-4">Baseline RAG</div>
|
| 223 |
+
<div className="body-md mb-6" style={{ minHeight: "80px", lineHeight: 1.65 }}>
|
| 224 |
+
{baseline.answer}
|
| 225 |
+
</div>
|
| 226 |
+
<div className="grid grid-cols-3 gap-4 pt-4" style={{ borderTop: "1px solid var(--color-hairline)" }}>
|
| 227 |
+
<div>
|
| 228 |
+
<div className="metric-value-sm" style={{ color: "#0072CE" }}>{baseline.tokens.toLocaleString()}</div>
|
| 229 |
+
<div className="metric-label">Tokens</div>
|
| 230 |
+
</div>
|
| 231 |
+
<div>
|
| 232 |
+
<div className="metric-value-sm" style={{ color: "#0072CE" }}>{baseline.latencyMs.toFixed(0)}ms</div>
|
| 233 |
+
<div className="metric-label">Latency</div>
|
| 234 |
+
</div>
|
| 235 |
+
<div>
|
| 236 |
+
<div className="metric-value-sm" style={{ color: "#0072CE" }}>${baseline.costUsd.toFixed(6)}</div>
|
| 237 |
+
<div className="metric-label">Cost</div>
|
| 238 |
+
</div>
|
| 239 |
+
</div>
|
| 240 |
+
</div>
|
| 241 |
+
|
| 242 |
+
{/* GraphRAG Card */}
|
| 243 |
+
<div className="card pipeline-graphrag" style={{ position: "relative" }}>
|
| 244 |
+
<div style={{
|
| 245 |
+
position: "absolute", top: "16px", right: "16px",
|
| 246 |
+
width: "8px", height: "8px", borderRadius: "50%",
|
| 247 |
+
background: "#FF6B00",
|
| 248 |
+
boxShadow: "0 0 8px rgba(255,107,0,0.4)",
|
| 249 |
+
}} />
|
| 250 |
+
<div className="caption-uppercase mb-1" style={{ color: "#FF6B00" }}>Pipeline B</div>
|
| 251 |
+
<div className="title-lg mb-4">GraphRAG</div>
|
| 252 |
+
<div className="body-md mb-6" style={{ minHeight: "80px", lineHeight: 1.65 }}>
|
| 253 |
+
{graphrag.answer}
|
| 254 |
+
</div>
|
| 255 |
+
<div className="grid grid-cols-3 gap-4 pt-4" style={{ borderTop: "1px solid var(--color-hairline)" }}>
|
| 256 |
+
<div>
|
| 257 |
+
<div className="metric-value-sm" style={{ color: "#FF6B00" }}>{graphrag.tokens.toLocaleString()}</div>
|
| 258 |
+
<div className="metric-label">Tokens</div>
|
| 259 |
+
</div>
|
| 260 |
+
<div>
|
| 261 |
+
<div className="metric-value-sm" style={{ color: "#FF6B00" }}>{graphrag.latencyMs.toFixed(0)}ms</div>
|
| 262 |
+
<div className="metric-label">Latency</div>
|
| 263 |
+
</div>
|
| 264 |
+
<div>
|
| 265 |
+
<div className="metric-value-sm" style={{ color: "#FF6B00" }}>${graphrag.costUsd.toFixed(6)}</div>
|
| 266 |
+
<div className="metric-label">Cost</div>
|
| 267 |
+
</div>
|
| 268 |
+
</div>
|
| 269 |
+
|
| 270 |
+
{/* Entities & Relations */}
|
| 271 |
+
{graphrag.entities.length > 0 && (
|
| 272 |
+
<div className="mt-4 pt-4" style={{ borderTop: "1px solid var(--color-hairline)" }}>
|
| 273 |
+
<div className="caption mb-2">Extracted Entities</div>
|
| 274 |
+
<div className="flex flex-wrap gap-1.5">
|
| 275 |
+
{graphrag.entities.map((e, i) => (
|
| 276 |
+
<span key={i} className="badge-outline" style={{ fontSize: "0.6875rem" }}>{e}</span>
|
| 277 |
+
))}
|
| 278 |
+
</div>
|
| 279 |
+
{graphrag.relations.length > 0 && (
|
| 280 |
+
<div className="mt-3">
|
| 281 |
+
<div className="caption mb-1">Reasoning Path</div>
|
| 282 |
+
{graphrag.relations.map((r, i) => (
|
| 283 |
+
<div key={i} className="body-sm" style={{
|
| 284 |
+
color: "var(--color-muted)", fontFamily: "var(--font-mono)",
|
| 285 |
+
fontSize: "0.75rem", padding: "2px 0",
|
| 286 |
+
}}>
|
| 287 |
+
🔗 {r}
|
| 288 |
+
</div>
|
| 289 |
+
))}
|
| 290 |
+
</div>
|
| 291 |
+
)}
|
| 292 |
+
</div>
|
| 293 |
+
)}
|
| 294 |
+
</div>
|
| 295 |
+
</div>
|
| 296 |
+
|
| 297 |
+
{/* Comparison Chart */}
|
| 298 |
+
{chartData.length > 0 && (
|
| 299 |
+
<div className="card">
|
| 300 |
+
<div className="title-md mb-6">Side-by-Side Metrics</div>
|
| 301 |
+
<ResponsiveContainer width="100%" height={300}>
|
| 302 |
+
<BarChart data={chartData} margin={{ top: 20, right: 30, left: 0, bottom: 0 }}>
|
| 303 |
+
<CartesianGrid strokeDasharray="3 3" stroke="#002B49" strokeOpacity={0.06} />
|
| 304 |
+
<XAxis dataKey="name" tick={{ fill: "#6c6a64", fontSize: 13 }} />
|
| 305 |
+
<YAxis tick={{ fill: "#6c6a64", fontSize: 12 }} />
|
| 306 |
+
<Tooltip contentStyle={{ background: "#faf9f5", border: "1px solid #e6dfd8", borderRadius: "10px" }} />
|
| 307 |
+
<Legend />
|
| 308 |
+
<Bar dataKey="Baseline" fill="#0072CE" radius={[6, 6, 0, 0]} />
|
| 309 |
+
<Bar dataKey="GraphRAG" fill="#FF6B00" radius={[6, 6, 0, 0]} />
|
| 310 |
+
</BarChart>
|
| 311 |
+
</ResponsiveContainer>
|
| 312 |
+
</div>
|
| 313 |
+
)}
|
| 314 |
+
</div>
|
| 315 |
+
)}
|
| 316 |
+
</div>
|
| 317 |
+
);
|
| 318 |
+
}
|