Skip to content

Commit 0e34630

Browse files
authored
Merge pull request #88 from liubchigo/main
✨Add info on model and limits
2 parents c55cc0a + 3352fe2 commit 0e34630

3 files changed

Lines changed: 341 additions & 6 deletions

File tree

src/App.tsx

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { Separator } from "@/components/ui/separator";
1111
import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart";
1212
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
1313
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet";
14+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
1415
import { Tooltip as UITooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
1516
import { DeploymentFooter } from "@/components/DeploymentFooter";
1617
import {
@@ -26,6 +27,7 @@ import {
2627
getDailyModelData,
2728
getPowerUsers,
2829
getPowerUserDailyData,
30+
COPILOT_PLANS,
2931
getPowerUserDailyBreakdown,
3032
getLastDateFromData
3133
} from "@/lib/utils";
@@ -41,6 +43,7 @@ function App() {
4143
const [selectedPowerUser, setSelectedPowerUser] = useState<string | null>(null);
4244
const [lastDateAvailable, setLastDateAvailable] = useState<string | null>(null);
4345
const [isDragging, setIsDragging] = useState(false);
46+
const [selectedPlan, setSelectedPlan] = useState<string>(COPILOT_PLANS.BUSINESS); // Default to Business
4447
const [isProcessing, setIsProcessing] = useState(false);
4548
const fileInputRef = useRef<HTMLInputElement>(null);
4649

@@ -316,6 +319,27 @@ function App() {
316319
}, {} as Record<string, string>);
317320
}, [uniqueModels]);
318321

322+
// Helper function to get plan limit based on selected plan
323+
const getPlanLimit = useCallback((item: ModelUsageSummary) => {
324+
let limit: number;
325+
switch (selectedPlan) {
326+
case COPILOT_PLANS.INDIVIDUAL:
327+
limit = item.individualPlanLimit;
328+
break;
329+
case COPILOT_PLANS.BUSINESS:
330+
limit = item.businessPlanLimit;
331+
break;
332+
case COPILOT_PLANS.ENTERPRISE:
333+
limit = item.enterprisePlanLimit;
334+
break;
335+
default:
336+
limit = item.businessPlanLimit;
337+
}
338+
339+
// For 0x multiplier models, show "Unlimited" despite having constant plan limits
340+
return item.multiplier === 0 ? "Unlimited" : limit.toLocaleString();
341+
}, [selectedPlan]);
342+
319343
return (
320344
<div className="container max-w-7xl mx-auto py-8 px-4 min-h-screen">
321345
<header className="mb-8">
@@ -672,7 +696,22 @@ function App() {
672696
{/* Model Usage Table */}
673697
<div className="mb-6">
674698
<Card className="p-5">
675-
<h3 className="text-md font-medium mb-3">Requests per Model</h3>
699+
<div className="flex items-center justify-between mb-3">
700+
<h3 className="text-md font-medium">Requests per Model</h3>
701+
<div className="flex items-center gap-2">
702+
<span className="text-sm text-muted-foreground">Plan Type:</span>
703+
<Select value={selectedPlan} onValueChange={setSelectedPlan}>
704+
<SelectTrigger className="w-32">
705+
<SelectValue placeholder="Select plan" />
706+
</SelectTrigger>
707+
<SelectContent>
708+
<SelectItem value={COPILOT_PLANS.INDIVIDUAL}>Individual</SelectItem>
709+
<SelectItem value={COPILOT_PLANS.BUSINESS}>Business</SelectItem>
710+
<SelectItem value={COPILOT_PLANS.ENTERPRISE}>Enterprise</SelectItem>
711+
</SelectContent>
712+
</Select>
713+
</div>
714+
</div>
676715
<div className="overflow-auto max-h-60">
677716
<Table>
678717
<TableHeader>
@@ -681,6 +720,9 @@ function App() {
681720
<TableHead className="text-right">Total Requests</TableHead>
682721
<TableHead className="text-right">Compliant</TableHead>
683722
<TableHead className="text-right">Exceeding</TableHead>
723+
<TableHead className="text-right">Multiplier</TableHead>
724+
<TableHead className="text-right">Plan Limit</TableHead>
725+
<TableHead className="text-right">Excess Cost</TableHead>
684726
</TableRow>
685727
</TableHeader>
686728
<TableBody>
@@ -690,6 +732,32 @@ function App() {
690732
<TableCell className="text-right">{item.totalRequests.toLocaleString(undefined, {maximumFractionDigits: 2, minimumFractionDigits: 0})}</TableCell>
691733
<TableCell className="text-right">{item.compliantRequests.toLocaleString(undefined, {maximumFractionDigits: 2, minimumFractionDigits: 0})}</TableCell>
692734
<TableCell className="text-right">{item.exceedingRequests.toLocaleString(undefined, {maximumFractionDigits: 2, minimumFractionDigits: 0})}</TableCell>
735+
<TableCell className="text-right">{item.multiplier}x</TableCell>
736+
<TableCell className="text-right">{getPlanLimit(item)}</TableCell>
737+
<TableCell className="text-right">${item.excessCost.toFixed(2)}</TableCell>
738+
</TableRow>
739+
))}
740+
</TableBody>
741+
</Table>
742+
</div>
743+
</Card>
744+
</div>
745+
746+
{/* Models List Card */}
747+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-6">
748+
<Card className="p-5">
749+
<h3 className="text-md font-medium mb-3">Models List</h3>
750+
<div className="overflow-auto max-h-60">
751+
<Table>
752+
<TableHeader>
753+
<TableRow>
754+
<TableHead>Model Name</TableHead>
755+
</TableRow>
756+
</TableHeader>
757+
<TableBody>
758+
{uniqueModels.map((model) => (
759+
<TableRow key={model}>
760+
<TableCell>{model}</TableCell>
693761
</TableRow>
694762
))}
695763
</TableBody>
@@ -772,7 +840,6 @@ function App() {
772840
stroke="#10b981"
773841
strokeWidth={2}
774842
activeDot={{ r: 6 }}
775-
stackId="1"
776843
/>
777844
<Line
778845
key="exceeding"
@@ -782,7 +849,6 @@ function App() {
782849
stroke="#ef4444"
783850
strokeWidth={2}
784851
activeDot={{ r: 6 }}
785-
stackId="1"
786852
/>
787853
</LineChart>
788854
</ChartContainer>
@@ -800,7 +866,10 @@ function App() {
800866
<Separator className="mb-6" />
801867
<div className="bg-card p-4 rounded-lg border">
802868
<ChartContainer
803-
config={getModelColors()}
869+
config={Object.entries(getModelColors()).reduce((acc, [model, color]) => {
870+
acc[model] = { color };
871+
return acc;
872+
}, {} as Record<string, { color: string }>)}
804873
className="h-[500px] w-full"
805874
>
806875
<BarChart data={barChartData()}>

src/lib/utils.ts

Lines changed: 97 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,15 @@ export function parseCSV(csv: string): CopilotUsageData[] {
122122

123123
export interface ModelUsageSummary {
124124
model: string;
125+
displayName: string; // For grouping default models
125126
totalRequests: number;
126127
compliantRequests: number;
127128
exceedingRequests: number;
129+
multiplier: number;
130+
individualPlanLimit: number;
131+
businessPlanLimit: number;
132+
enterprisePlanLimit: number;
133+
excessCost: number; // Cost for exceeding requests
128134
}
129135

130136
export interface DailyModelData {
@@ -168,11 +174,20 @@ export function getModelUsageSummary(data: CopilotUsageData[]): ModelUsageSummar
168174

169175
data.forEach(item => {
170176
if (!summary[item.model]) {
177+
const multiplier = MODEL_MULTIPLIERS[item.model] || 1;
178+
const displayName = DEFAULT_MODELS.includes(item.model) ? 'Default' : item.model;
179+
171180
summary[item.model] = {
172181
model: item.model,
182+
displayName,
173183
totalRequests: 0,
174184
compliantRequests: 0,
175-
exceedingRequests: 0
185+
exceedingRequests: 0,
186+
multiplier,
187+
individualPlanLimit: PLAN_MONTHLY_LIMITS[COPILOT_PLANS.INDIVIDUAL],
188+
businessPlanLimit: PLAN_MONTHLY_LIMITS[COPILOT_PLANS.BUSINESS],
189+
enterprisePlanLimit: PLAN_MONTHLY_LIMITS[COPILOT_PLANS.ENTERPRISE],
190+
excessCost: 0
176191
};
177192
}
178193

@@ -185,8 +200,39 @@ export function getModelUsageSummary(data: CopilotUsageData[]): ModelUsageSummar
185200
}
186201
});
187202

203+
// Calculate excess costs and group default models
204+
const summaryArray = Object.values(summary);
205+
const groupedSummary: Record<string, ModelUsageSummary> = {};
206+
207+
summaryArray.forEach(item => {
208+
const key = item.displayName;
209+
210+
if (!groupedSummary[key]) {
211+
groupedSummary[key] = {
212+
...item,
213+
model: key === 'Default' ? 'Default (GPT-4o, GPT-4.1)' : item.model
214+
};
215+
} else {
216+
// Merge default models
217+
groupedSummary[key].totalRequests += item.totalRequests;
218+
groupedSummary[key].compliantRequests += item.compliantRequests;
219+
groupedSummary[key].exceedingRequests += item.exceedingRequests;
220+
}
221+
222+
// For grouped default models, ensure multiplier is 0 and limits use constant values
223+
if (key === 'Default') {
224+
groupedSummary[key].multiplier = 0;
225+
groupedSummary[key].individualPlanLimit = PLAN_MONTHLY_LIMITS[COPILOT_PLANS.INDIVIDUAL];
226+
groupedSummary[key].businessPlanLimit = PLAN_MONTHLY_LIMITS[COPILOT_PLANS.BUSINESS];
227+
groupedSummary[key].enterprisePlanLimit = PLAN_MONTHLY_LIMITS[COPILOT_PLANS.ENTERPRISE];
228+
}
229+
230+
// Calculate excess cost
231+
groupedSummary[key].excessCost = groupedSummary[key].exceedingRequests * groupedSummary[key].multiplier * EXCESS_REQUEST_COST;
232+
});
233+
188234
// Convert to array and sort by total requests (descending)
189-
return Object.values(summary).sort((a, b) => b.totalRequests - a.totalRequests);
235+
return Object.values(groupedSummary).sort((a, b) => b.totalRequests - a.totalRequests);
190236
}
191237

192238
export function getDailyModelData(data: CopilotUsageData[]): DailyModelData[] {
@@ -241,6 +287,55 @@ export interface PowerUserDailyBreakdown {
241287

242288

243289

290+
// Copilot plan constants
291+
export const COPILOT_PLANS = {
292+
INDIVIDUAL: 'Individual',
293+
BUSINESS: 'Business',
294+
ENTERPRISE: 'Enterprise'
295+
} as const;
296+
297+
export const PLAN_MONTHLY_LIMITS = {
298+
[COPILOT_PLANS.INDIVIDUAL]: 50,
299+
[COPILOT_PLANS.BUSINESS]: 300,
300+
[COPILOT_PLANS.ENTERPRISE]: 1000
301+
} as const;
302+
303+
// Model multipliers based on GitHub documentation (for paid plans)
304+
export const MODEL_MULTIPLIERS: Record<string, number> = {
305+
// Default models (0x multiplier for paid plans)
306+
'gpt-4o-2024-11-20': 0,
307+
'gpt-4.1-2025-04-14': 0,
308+
'gpt-4o': 0,
309+
'gpt-4.1': 0,
310+
// GPT-4.5 models
311+
'gpt-4.5': 50,
312+
// Vision models
313+
'gpt-4.1-vision': 0,
314+
// Claude models
315+
'claude-sonnet-3.5': 1,
316+
'claude-sonnet-3.7': 1,
317+
'claude-sonnet-3.7-thinking': 1.25,
318+
'claude-sonnet-4': 1,
319+
'claude-opus-4': 10,
320+
// Gemini models
321+
'gemini-2.0-flash': 0.25,
322+
'gemini-2.5-pro': 1,
323+
// O-series models
324+
'o1': 10,
325+
'o3': 1,
326+
'o3-mini': 0.33,
327+
'o3-mini-2025-01-31': 0.33,
328+
'o4-mini': 0.33,
329+
'o4-mini-2025-04-16': 0.33,
330+
// Add other models as needed - fallback to 1x for unknown models
331+
};
332+
333+
// Default models that should be grouped
334+
export const DEFAULT_MODELS = ['gpt-4o-2024-11-20', 'gpt-4.1-2025-04-14'];
335+
336+
// Cost per excess request (in USD) for premium requests
337+
export const EXCESS_REQUEST_COST = 0.04; // $0.04 per excess request
338+
244339
export function getPowerUsers(data: CopilotUsageData[]): PowerUserSummary {
245340
// First, aggregate total requests per user
246341
const userTotals: Record<string, number> = {};

0 commit comments

Comments
 (0)