import { useState, useCallback } from "react";
import {
BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Cell,
RadarChart, PolarGrid, PolarAngleAxis, Radar, Legend
} from "recharts";
import { Zap, Database, GitBranch, Search, TrendingDown, Award, Clock, DollarSign, CheckCircle, AlertCircle, Loader } from "lucide-react";
// Use VITE_API_URL if set, otherwise use current origin (HuggingFace Spaces) or localhost:8001 (dev)
const API_BASE = import.meta.env.VITE_API_URL || (typeof window !== 'undefined' ? window.location.origin : "http://localhost:8001");
const PIPELINES = [
{ id: "llm_only", label: "LLM Only", icon: Zap, color: "#ef4444", dimColor: "rgba(239,68,68,0.12)", desc: "No retrieval · Parametric only" },
{ id: "basic_rag", label: "Basic RAG", icon: Database, color: "#f97316", dimColor: "rgba(249,115,22,0.12)", desc: "Vector search · Top-K chunks" },
{ id: "graph_rag", label: "GraphRAG", icon: GitBranch, color: "#22c55e", dimColor: "rgba(34,197,94,0.12)", desc: "TigerGraph · Subgraph traversal" },
];
const SAMPLE_QUESTIONS = [
"What is the relationship between transformer architecture and attention mechanisms?",
"How does GraphRAG reduce token consumption compared to Basic RAG?",
"What metrics are used to evaluate answer quality in RAG systems?",
"How does TigerGraph support multi-hop reasoning in knowledge graphs?",
"What are the cost implications of token reduction at production scale?",
];
// ─── Stat Card ───────────────────────────────────────────────────────────────
function StatCard({ label, value, sub, color = "#f97316", icon: Icon }) {
return (
{label}
{Icon && }
{value}
{sub &&
{sub}
}
);
}
// ─── Pipeline Answer Card ────────────────────────────────────────────────────
function PipelineCard({ pipeline, data, loading }) {
const { label, color, dimColor, desc, icon: Icon } = pipeline;
return (
{/* Header */}
{/* Metrics Row */}
{data && (
{[
{ icon: Database, label: "Tokens", val: data.total_tokens?.toLocaleString() },
{ icon: Clock, label: "Latency", val: `${data.latency_ms?.toFixed(0)}ms` },
{ icon: DollarSign, label: "Cost", val: `$${(data.cost_usd * 1000).toFixed(3)}/k` },
].map(({ icon: MIcon, label: ml, val }) => (
))}
)}
{/* Answer */}
{loading ? (
Running pipeline...
) : data ? (
{data.answer}
) : (
Answer will appear here after you submit a query.
)}
{/* Judge Score */}
{data?.judge_score !== undefined && (
Judge Score: {data.judge_score}/10
)}
);
}
// ─── Token Bar Chart ─────────────────────────────────────────────────────────
function TokenChart({ results }) {
if (!results) return null;
const data = [
{ name: "LLM Only", tokens: results.llm_only.total_tokens, fill: "#ef4444" },
{ name: "Basic RAG", tokens: results.basic_rag.total_tokens, fill: "#f97316" },
{ name: "GraphRAG", tokens: results.graph_rag.total_tokens, fill: "#22c55e" },
];
return (
Token Usage Comparison
{data.map((d, i) => | )}
);
}
// ─── Accuracy Radar ───────────────────────────────────────────────────────────
function AccuracyRadar({ judgeScores }) {
if (!judgeScores) return null;
const { basic_rag_score: b, graph_rag_score: g } = judgeScores;
const data = [
{ axis: "Correctness", basic: b.correctness, graph: g.correctness },
{ axis: "Completeness", basic: b.completeness, graph: g.completeness },
{ axis: "Clarity", basic: b.clarity, graph: g.clarity },
{ axis: "Relevance", basic: b.relevance, graph: g.relevance },
];
return (
Answer Quality (LLM-as-Judge)
);
}
// ─── Reduction Badge ─────────────────────────────────────────────────────────
function ReductionBadge({ label, pct, icon: Icon }) {
const good = pct > 0;
const color = good ? "#22c55e" : "#ef4444";
return (
{label}
{good ? "↓" : "↑"}{Math.abs(pct).toFixed(1)}%
vs Basic RAG
);
}
// ─── Main App ─────────────────────────────────────────────────────────────────
export default function App() {
const [question, setQuestion] = useState("");
const [groundTruth, setGroundTruth] = useState("");
const [loading, setLoading] = useState(false);
const [results, setResults] = useState(null);
const [error, setError] = useState(null);
const [sessionStats, setSessionStats] = useState(null);
const fetchSessionStats = useCallback(async () => {
try {
const r = await fetch(`${API_BASE}/stats/session`);
if (r.ok) setSessionStats(await r.json());
} catch {}
}, []);
const handleQuery = useCallback(async () => {
if (!question.trim() || loading) return;
setLoading(true);
setError(null);
setResults(null);
try {
const res = await fetch(`${API_BASE}/query/compare`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ question: question.trim(), ground_truth: groundTruth, run_judge: true }),
});
if (!res.ok) throw new Error(`API error ${res.status}`);
const data = await res.json();
// Attach judge scores to individual pipelines for display
if (data.judge_scores) {
const js = data.judge_scores;
data.basic_rag.judge_score = js.basic_rag_score?.overall;
data.graph_rag.judge_score = js.graph_rag_score?.overall;
}
setResults(data);
fetchSessionStats();
} catch (e) {
setError(e.message);
} finally {
setLoading(false);
}
}, [question, groundTruth, loading, fetchSessionStats]);
return (
{/* Header */}
🐯
GraphRAG Inference Dashboard
TigerGraph Hackathon · 3-Pipeline Comparison
{sessionStats && (
QUERIES RUN
{sessionStats.total_queries}
TOKENS SAVED
{sessionStats.total_tokens_saved?.toLocaleString()}
AVG REDUCTION
{sessionStats.avg_token_reduction_pct}%
)}
{/* Query Input */}
Enter Your Query
{/* Sample questions */}
{SAMPLE_QUESTIONS.map((q, i) => (
))}
{/* Error */}
{error && (
{error} — Is the backend running? (
uvicorn api.server:app --reload)
)}
{/* Results */}
{results && (
<>
{/* Reduction Badges */}
{/* Charts Row */}
>
)}
{/* 3 Pipeline Cards */}
{PIPELINES.map(p => (
))}
{/* Metric Summary Table */}
{results && (
📊 Full Metrics Table
{["Metric", "LLM Only", "Basic RAG", "GraphRAG", "Graph vs Basic"].map(h => (
| {h} |
))}
{[
{ label: "Prompt Tokens", llm: results.llm_only.prompt_tokens, basic: results.basic_rag.prompt_tokens, graph: results.graph_rag.prompt_tokens, better: "lower" },
{ label: "Completion Tokens", llm: results.llm_only.completion_tokens, basic: results.basic_rag.completion_tokens, graph: results.graph_rag.completion_tokens, better: "lower" },
{ label: "Total Tokens", llm: results.llm_only.total_tokens, basic: results.basic_rag.total_tokens, graph: results.graph_rag.total_tokens, better: "lower" },
{ label: "Latency (ms)", llm: results.llm_only.latency_ms?.toFixed(0), basic: results.basic_rag.latency_ms?.toFixed(0), graph: results.graph_rag.latency_ms?.toFixed(0), better: "lower" },
{ label: "Cost ($)", llm: results.llm_only.cost_usd?.toFixed(5), basic: results.basic_rag.cost_usd?.toFixed(5), graph: results.graph_rag.cost_usd?.toFixed(5), better: "lower" },
{ label: "Judge Score", llm: "—", basic: results.judge_scores?.basic_rag_score?.overall?.toFixed(1) ?? "—", graph: results.judge_scores?.graph_rag_score?.overall?.toFixed(1) ?? "—", better: "higher" },
].map(row => {
const basicNum = parseFloat(row.basic);
const graphNum = parseFloat(row.graph);
const graphBetter = row.better === "lower" ? graphNum < basicNum : graphNum > basicNum;
const pctDiff = basicNum > 0 ? ((graphNum - basicNum) / basicNum * 100).toFixed(1) : null;
return (
| {row.label} |
{row.llm} |
{row.basic} |
{row.graph} |
{pctDiff !== null && (
{pctDiff > 0 ? "+" : ""}{pctDiff}%
)}
|
);
})}
)}
{/* Footer */}
);
}