Skip to content

Commit 8cf66c2

Browse files
committed
Generated by Spark: in the models used panel, show the models used as a table to the right of the panel. Each model is a new row in the table with the count per model
in the totla request panel, also show the table of request per model in a table to the right of the panel add a new bar chart with a bar per model, per day in the data
1 parent c453490 commit 8cf66c2

3 files changed

Lines changed: 277 additions & 8 deletions

File tree

src/App.tsx

Lines changed: 203 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,31 @@ import { useState, useCallback, useRef, DragEvent } from "react";
22
import { Upload } from "@phosphor-icons/react";
33
import { toast } from "sonner";
44
import {
5-
LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer
5+
LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer,
6+
BarChart, Bar, Cell
67
} from "recharts";
78
import { Card } from "@/components/ui/card";
89
import { Button } from "@/components/ui/button";
910
import { Separator } from "@/components/ui/separator";
1011
import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart";
11-
import { AggregatedData, CopilotUsageData, aggregateDataByDay, parseCSV } from "@/lib/utils";
12+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
13+
import {
14+
AggregatedData,
15+
CopilotUsageData,
16+
ModelUsageSummary,
17+
DailyModelData,
18+
aggregateDataByDay,
19+
parseCSV,
20+
getModelUsageSummary,
21+
getDailyModelData
22+
} from "@/lib/utils";
1223

1324
function App() {
1425
const [data, setData] = useState<CopilotUsageData[] | null>(null);
1526
const [aggregatedData, setAggregatedData] = useState<AggregatedData[]>([]);
1627
const [uniqueModels, setUniqueModels] = useState<string[]>([]);
28+
const [modelSummary, setModelSummary] = useState<ModelUsageSummary[]>([]);
29+
const [dailyModelData, setDailyModelData] = useState<DailyModelData[]>([]);
1730
const [isDragging, setIsDragging] = useState(false);
1831
const fileInputRef = useRef<HTMLInputElement>(null);
1932

@@ -37,12 +50,22 @@ function App() {
3750
const aggregated = aggregateDataByDay(parsedData);
3851
setAggregatedData(aggregated);
3952

53+
// Get model usage summary
54+
const summary = getModelUsageSummary(parsedData);
55+
setModelSummary(summary);
56+
57+
// Get daily model data for bar chart
58+
const dailyData = getDailyModelData(parsedData);
59+
setDailyModelData(dailyData);
60+
4061
toast.success(`Loaded ${parsedData.length} records successfully`);
4162
} catch (error) {
4263
console.error("Error parsing CSV:", error);
4364
toast.error("Failed to parse CSV file. Please check the format.");
4465
setData(null);
4566
setAggregatedData([]);
67+
setModelSummary([]);
68+
setDailyModelData([]);
4669
}
4770
};
4871

@@ -118,6 +141,72 @@ function App() {
118141
a.date.localeCompare(b.date)
119142
);
120143
}, [aggregatedData]);
144+
145+
// Generate bar chart data grouped by date and model
146+
const barChartData = useCallback(() => {
147+
if (!dailyModelData.length) return [];
148+
149+
// Group by date first
150+
const groupedByDate: Record<string, any> = {};
151+
152+
// Get all unique dates and models
153+
const dates = new Set<string>();
154+
const models = new Set<string>();
155+
156+
dailyModelData.forEach(item => {
157+
dates.add(item.date);
158+
models.add(item.model);
159+
});
160+
161+
// Create entries for each date
162+
dates.forEach(date => {
163+
groupedByDate[date] = {
164+
date,
165+
};
166+
167+
// Initialize models with zero
168+
models.forEach(model => {
169+
groupedByDate[date][model] = 0;
170+
});
171+
});
172+
173+
// Fill in the actual data
174+
dailyModelData.forEach(item => {
175+
groupedByDate[item.date][item.model] = item.requests;
176+
});
177+
178+
// Convert to array sorted by date
179+
return Object.values(groupedByDate).sort((a: any, b: any) =>
180+
a.date.localeCompare(b.date)
181+
);
182+
}, [dailyModelData]);
183+
184+
// Get unique model names for bar chart
185+
const getUniqueModelsForBarChart = useCallback(() => {
186+
return uniqueModels;
187+
}, [uniqueModels]);
188+
189+
// Generate colors for models in bar chart
190+
const getModelColors = useCallback(() => {
191+
// Use a set of predefined colors that are visually distinct
192+
const colors = [
193+
"#4285F4", // Blue
194+
"#EA4335", // Red
195+
"#FBBC05", // Yellow
196+
"#34A853", // Green
197+
"#8E44AD", // Purple
198+
"#F39C12", // Orange
199+
"#16A085", // Teal
200+
"#E74C3C", // Red-Orange
201+
"#3498DB", // Light Blue
202+
"#1ABC9C" // Turquoise
203+
];
204+
205+
return uniqueModels.reduce((acc, model, index) => {
206+
acc[model] = colors[index % colors.length];
207+
return acc;
208+
}, {} as Record<string, string>);
209+
}, [uniqueModels]);
121210

122211
return (
123212
<div className="container max-w-7xl mx-auto py-8 px-4 min-h-screen">
@@ -167,7 +256,7 @@ function App() {
167256
<div>
168257
<h2 className="text-2xl font-semibold mb-2">Usage Statistics</h2>
169258
<Separator className="mb-4" />
170-
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
259+
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
171260
<Card>
172261
<div className="p-5">
173262
<h3 className="text-sm font-medium text-muted-foreground mb-1">Total Requests</h3>
@@ -190,9 +279,55 @@ function App() {
190279
<p className="text-2xl font-bold">
191280
{uniqueModels.length}
192281
</p>
193-
<div className="mt-2 text-xs text-muted-foreground">
194-
{uniqueModels.join(", ")}
195-
</div>
282+
</div>
283+
</Card>
284+
</div>
285+
286+
{/* Model Usage Table */}
287+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-6">
288+
<Card className="p-5">
289+
<h3 className="text-md font-medium mb-3">Requests per Model</h3>
290+
<div className="overflow-auto max-h-60">
291+
<Table>
292+
<TableHeader>
293+
<TableRow>
294+
<TableHead>Model</TableHead>
295+
<TableHead className="text-right">Total Requests</TableHead>
296+
<TableHead className="text-right">Compliant</TableHead>
297+
<TableHead className="text-right">Exceeding</TableHead>
298+
</TableRow>
299+
</TableHeader>
300+
<TableBody>
301+
{modelSummary.map((item) => (
302+
<TableRow key={item.model}>
303+
<TableCell className="font-medium">{item.model}</TableCell>
304+
<TableCell className="text-right">{item.totalRequests.toLocaleString()}</TableCell>
305+
<TableCell className="text-right">{item.compliantRequests.toLocaleString()}</TableCell>
306+
<TableCell className="text-right">{item.exceedingRequests.toLocaleString()}</TableCell>
307+
</TableRow>
308+
))}
309+
</TableBody>
310+
</Table>
311+
</div>
312+
</Card>
313+
314+
<Card className="p-5">
315+
<h3 className="text-md font-medium mb-3">Models List</h3>
316+
<div className="overflow-auto max-h-60">
317+
<Table>
318+
<TableHeader>
319+
<TableRow>
320+
<TableHead>Model Name</TableHead>
321+
</TableRow>
322+
</TableHeader>
323+
<TableBody>
324+
{uniqueModels.map((model) => (
325+
<TableRow key={model}>
326+
<TableCell>{model}</TableCell>
327+
</TableRow>
328+
))}
329+
</TableBody>
330+
</Table>
196331
</div>
197332
</Card>
198333
</div>
@@ -201,7 +336,7 @@ function App() {
201336
<div>
202337
<h2 className="text-2xl font-semibold mb-2">Daily Usage Overview</h2>
203338
<Separator className="mb-6" />
204-
<div className="bg-card p-4 rounded-lg border">
339+
<div className="bg-card p-4 rounded-lg border mb-8">
205340
<ChartContainer
206341
config={{
207342
compliant: { color: "#10b981" }, // green
@@ -278,11 +413,71 @@ function App() {
278413
</LineChart>
279414
</ChartContainer>
280415
</div>
416+
417+
{/* Bar Chart - Requests per Model per Day */}
418+
<h2 className="text-2xl font-semibold mb-2">Requests per Model per Day</h2>
419+
<Separator className="mb-6" />
420+
<div className="bg-card p-4 rounded-lg border">
421+
<ChartContainer
422+
config={getModelColors()}
423+
className="h-[500px] w-full"
424+
>
425+
<BarChart data={barChartData()}>
426+
<CartesianGrid strokeDasharray="3 3" opacity={0.2} />
427+
<XAxis
428+
dataKey="date"
429+
tick={{ fill: 'var(--foreground)' }}
430+
tickLine={{ stroke: 'var(--border)' }}
431+
/>
432+
<YAxis
433+
tick={{ fill: 'var(--foreground)' }}
434+
tickLine={{ stroke: 'var(--border)' }}
435+
/>
436+
<Tooltip
437+
content={({ active, payload, label }) => {
438+
if (active && payload && payload.length) {
439+
return (
440+
<div className="border rounded-lg bg-background shadow-lg p-3">
441+
<div className="font-medium mb-2">{label}</div>
442+
<div className="space-y-2">
443+
{payload.map((entry, index) => (
444+
<div key={`item-${index}`} className="flex justify-between items-center gap-4">
445+
<div className="flex items-center gap-1.5">
446+
<div
447+
className="w-2 h-2 rounded-full"
448+
style={{ backgroundColor: entry.color }}
449+
/>
450+
<span>{entry.name}:</span>
451+
</div>
452+
<div className="font-medium">{entry.value}</div>
453+
</div>
454+
))}
455+
</div>
456+
</div>
457+
);
458+
}
459+
return null;
460+
}}
461+
/>
462+
<Legend />
463+
464+
{/* Generate a bar for each model */}
465+
{getUniqueModelsForBarChart().map((model, index) => (
466+
<Bar
467+
key={model}
468+
dataKey={model}
469+
name={model}
470+
fill={getModelColors()[model]}
471+
/>
472+
))}
473+
</BarChart>
474+
</ChartContainer>
475+
</div>
281476
</div>
282477
</div>
283478
)}
284479
</div>
285480
);
286481
}
287482

288-
export default App;
483+
export default App;

src/lib/utils.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,19 @@ export function parseCSV(csv: string): CopilotUsageData[] {
5353
});
5454
}
5555

56+
export interface ModelUsageSummary {
57+
model: string;
58+
totalRequests: number;
59+
compliantRequests: number;
60+
exceedingRequests: number;
61+
}
62+
63+
export interface DailyModelData {
64+
date: string;
65+
model: string;
66+
requests: number;
67+
}
68+
5669
export function aggregateDataByDay(data: CopilotUsageData[]): AggregatedData[] {
5770
const aggregated: Record<string, AggregatedData> = {};
5871

@@ -82,3 +95,54 @@ export function aggregateDataByDay(data: CopilotUsageData[]): AggregatedData[] {
8295
return a.model.localeCompare(b.model);
8396
});
8497
}
98+
99+
export function getModelUsageSummary(data: CopilotUsageData[]): ModelUsageSummary[] {
100+
const summary: Record<string, ModelUsageSummary> = {};
101+
102+
data.forEach(item => {
103+
if (!summary[item.model]) {
104+
summary[item.model] = {
105+
model: item.model,
106+
totalRequests: 0,
107+
compliantRequests: 0,
108+
exceedingRequests: 0
109+
};
110+
}
111+
112+
summary[item.model].totalRequests += item.requestsUsed;
113+
114+
if (item.exceedsQuota) {
115+
summary[item.model].exceedingRequests += item.requestsUsed;
116+
} else {
117+
summary[item.model].compliantRequests += item.requestsUsed;
118+
}
119+
});
120+
121+
// Convert to array and sort by total requests (descending)
122+
return Object.values(summary).sort((a, b) => b.totalRequests - a.totalRequests);
123+
}
124+
125+
export function getDailyModelData(data: CopilotUsageData[]): DailyModelData[] {
126+
const aggregated: Record<string, DailyModelData> = {};
127+
128+
data.forEach(item => {
129+
const date = item.timestamp.toISOString().split('T')[0];
130+
const key = `${date}-${item.model}`;
131+
132+
if (!aggregated[key]) {
133+
aggregated[key] = {
134+
date,
135+
model: item.model,
136+
requests: 0
137+
};
138+
}
139+
140+
aggregated[key].requests += item.requestsUsed;
141+
});
142+
143+
// Convert to array and sort by date
144+
return Object.values(aggregated).sort((a, b) => {
145+
if (a.date !== b.date) return a.date.localeCompare(b.date);
146+
return a.model.localeCompare(b.model);
147+
});
148+
}

src/prd.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,16 @@
3434
- Why: Converts raw timestamp data into meaningful daily summaries
3535
- Success: Correctly groups and counts requests by day, model, and quota status
3636

37+
4. **Model Usage Statistics**
38+
- What: Display detailed model usage statistics in tabular format
39+
- Why: Provides granular insights into which models are being used and their quota status
40+
- Success: Clear presentation of model-specific usage data in an easily scannable table format
41+
42+
5. **Bar Chart Visualization**
43+
- What: Visualize model usage per day with multi-colored bar chart
44+
- Why: Offers an alternative visualization that highlights model distribution over time
45+
- Success: Clearly distinguishes between different models and shows their relative usage per day
46+
3747
## Design Direction
3848

3949
### Visual Tone & Identity

0 commit comments

Comments
 (0)