File size: 6,476 Bytes
18b47fb 7229c6f 18b47fb 7229c6f 18b47fb 7229c6f 18b47fb 7229c6f 18b47fb 7229c6f 18b47fb 7229c6f 18b47fb 7229c6f 18b47fb 7229c6f 18b47fb 7229c6f 18b47fb 7229c6f 18b47fb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | "use client";
import { useState, useMemo } from "react";
import {
XAxis, YAxis, CartesianGrid,
Tooltip, ResponsiveContainer, Legend,
AreaChart, Area,
} from "recharts";
const MODELS = [
{ id: "claude-sonnet", label: "Claude Sonnet 4", inputPer1k: 0.003, outputPer1k: 0.015 },
{ id: "claude-haiku", label: "Claude Haiku 4", inputPer1k: 0.00025, outputPer1k: 0.00125 },
{ id: "gpt-4o-mini", label: "GPT-4o-mini", inputPer1k: 0.00015, outputPer1k: 0.0006 },
{ id: "gpt-4o", label: "GPT-4o", inputPer1k: 0.0025, outputPer1k: 0.01 },
];
export function CostAnalysis() {
const [numQueries, setNumQueries] = useState(10000);
const [modelIdx, setModelIdx] = useState(0);
const model = MODELS[modelIdx];
const baselineAvgTokens = 950;
const graphragAvgTokens = 2400;
const baselineCostPerQ = (800 / 1000) * model.inputPer1k + (150 / 1000) * model.outputPer1k;
const graphragCostPerQ = (2200 / 1000) * model.inputPer1k + (200 / 1000) * model.outputPer1k;
const cumulativeData = useMemo(() => {
const points: { queries: number; Baseline: number; GraphRAG: number }[] = [];
const step = Math.max(Math.floor(numQueries / 50), 1);
for (let q = 0; q <= numQueries; q += step) {
points.push({
queries: q,
Baseline: +(baselineCostPerQ * q).toFixed(4),
GraphRAG: +(graphragCostPerQ * q).toFixed(4),
});
}
return points;
}, [numQueries, baselineCostPerQ, graphragCostPerQ]);
return (
<div>
{/* Controls */}
<div className="card mb-6">
<div className="display-sm mb-4">Cost & Token Projections</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div>
<label className="caption">Number of Queries</label>
<input type="range" min={100} max={100000} step={100}
value={numQueries} onChange={(e) => setNumQueries(+e.target.value)}
className="w-full mt-2 accent-[#FF6B00]" />
<div className="body-sm font-mono mt-1">{numQueries.toLocaleString()}</div>
</div>
<div>
<label className="caption">LLM Model</label>
<div className="flex flex-wrap gap-2 mt-2">
{MODELS.map((m, i) => (
<button key={m.id}
className={i === modelIdx ? "badge-orange" : "badge-outline cursor-pointer"}
onClick={() => setModelIdx(i)} style={{ fontSize: "0.75rem" }}>
{m.label}
</button>
))}
</div>
</div>
<div>
<label className="caption">Token Ratio</label>
<div className="metric-value-sm mt-2" style={{ color: "#cc785c" }}>
{(graphragAvgTokens / baselineAvgTokens).toFixed(1)}x
</div>
<div className="metric-label">GraphRAG / Baseline</div>
</div>
</div>
</div>
{/* Summary Cards */}
<div className="grid grid-cols-2 lg:grid-cols-5 gap-4 mb-6">
{[
{ label: "Cost/Query (Baseline)", value: "$" + baselineCostPerQ.toFixed(6), color: "#0072CE" },
{ label: "Cost/Query (GraphRAG)", value: "$" + graphragCostPerQ.toFixed(6), color: "#FF6B00" },
{ label: `Total (${(numQueries / 1000).toFixed(0)}K)`, value: "$" + (baselineCostPerQ * numQueries).toFixed(2), color: "#0072CE" },
{ label: `Total (${(numQueries / 1000).toFixed(0)}K)`, value: "$" + (graphragCostPerQ * numQueries).toFixed(2), color: "#FF6B00" },
{ label: "Annual (1K qpd)", value: "$" + (graphragCostPerQ * 1000 * 365).toFixed(0), color: "#cc785c" },
].map((m, i) => (
<div key={i} className="card-cream" style={{ padding: "16px", textAlign: "center" }}>
<div className="metric-value-sm" style={{ color: m.color, fontSize: "1.125rem" }}>{m.value}</div>
<div className="metric-label">{m.label}</div>
</div>
))}
</div>
{/* Cumulative Cost Chart */}
<div className="card mb-6">
<div className="title-md mb-4">Cumulative Cost — {model.label}</div>
<ResponsiveContainer width="100%" height={380}>
<AreaChart data={cumulativeData} margin={{ top: 10, right: 30, left: 10, bottom: 0 }}>
<defs>
<linearGradient id="baselineGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#0072CE" stopOpacity={0.2} />
<stop offset="95%" stopColor="#0072CE" stopOpacity={0} />
</linearGradient>
<linearGradient id="graphragGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#FF6B00" stopOpacity={0.2} />
<stop offset="95%" stopColor="#FF6B00" stopOpacity={0} />
</linearGradient>
</defs>
<CartesianGrid strokeDasharray="3 3" stroke="#002B49" strokeOpacity={0.06} />
<XAxis dataKey="queries" tick={{ fill: "#6c6a64", fontSize: 11 }}
tickFormatter={(v) => (Number(v) >= 1000 ? `${Number(v) / 1000}K` : String(v))} />
<YAxis tick={{ fill: "#6c6a64", fontSize: 11 }}
tickFormatter={(v) => `$${v}`} />
<Tooltip contentStyle={{ background: "#faf9f5", border: "1px solid #e6dfd8", borderRadius: "8px" }} />
<Legend />
<Area type="monotone" dataKey="Baseline" stroke="#0072CE" strokeWidth={2.5} fill="url(#baselineGrad)" />
<Area type="monotone" dataKey="GraphRAG" stroke="#FF6B00" strokeWidth={2.5} fill="url(#graphragGrad)" />
</AreaChart>
</ResponsiveContainer>
</div>
{/* Insight Card */}
<div className="card-coral">
<div className="display-sm" style={{ color: "white" }}>💡 Key Insight</div>
<p className="body-md mt-3" style={{ color: "rgba(255,255,255,0.9)", maxWidth: "640px" }}>
GraphRAG uses <strong>{(graphragAvgTokens / baselineAvgTokens).toFixed(1)}×</strong> more
tokens per query, but delivers <strong>+13% higher F1</strong> on complex multi-hop questions.
The Adaptive Router eliminates this overhead for simple queries by routing them to Baseline RAG —
achieving the best of both worlds.
</p>
<button className="btn btn-on-dark mt-4"
onClick={() => document.getElementById("live")?.scrollIntoView({ behavior: "smooth" })}>
Try Adaptive Routing →
</button>
</div>
</div>
);
}
|