muthuk1 commited on
Commit
18b47fb
·
verified ·
1 Parent(s): 370c4da

Add Tab 3: Cost Analysis with cumulative cost chart and projections

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