muthuk1 commited on
Commit
005833b
·
verified ·
1 Parent(s): 5fcdc45

🎨 Ultra premium UI: 6 separate pages with animations, bento grid, premium design

Browse files
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
+ &ldquo;{scenario.query}&rdquo;
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
+ &ldquo;{t.quote}&rdquo;
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
+ }