Skip to content

Commit 7c047f9

Browse files
committed
Add sql queries for weekly and all time usage
1 parent 51843df commit 7c047f9

File tree

2 files changed

+174
-40
lines changed

2 files changed

+174
-40
lines changed

web/src/app/agents/page.tsx

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,17 @@ interface AgentData {
3737
version: string
3838
created_at: string
3939
usage_count?: number
40-
total_spent?: number
41-
avg_cost_per_invocation?: number
42-
avg_response_time?: number
43-
40+
weekly_spent?: number // In dollars
41+
total_spent?: number // In dollars
42+
avg_cost_per_invocation?: number // In dollars
43+
unique_users?: number
44+
last_used?: string
4445
tags?: string[]
4546
}
4647

4748
const AgentStorePage = () => {
4849
const [searchQuery, setSearchQuery] = useState('')
49-
const [sortBy, setSortBy] = useState('usage')
50+
const [sortBy, setSortBy] = useState('cost')
5051

5152
// Fetch agents from the API
5253
const { data: agents = [], isLoading } = useQuery<AgentData[]>({
@@ -82,7 +83,7 @@ const AgentStorePage = () => {
8283
case 'name':
8384
return a.name.localeCompare(b.name)
8485
case 'cost':
85-
return (b.total_spent || 0) - (a.total_spent || 0)
86+
return (b.weekly_spent || 0) - (a.weekly_spent || 0)
8687
default:
8788
return 0
8889
}
@@ -131,10 +132,10 @@ const AgentStorePage = () => {
131132
<SelectValue placeholder="Sort by" />
132133
</SelectTrigger>
133134
<SelectContent>
134-
<SelectItem value="usage">Most Used</SelectItem>
135+
<SelectItem value="cost">Weekly Usage</SelectItem>
136+
<SelectItem value="usage">Total Runs</SelectItem>
135137
<SelectItem value="newest">Newest</SelectItem>
136138
<SelectItem value="name">Name</SelectItem>
137-
<SelectItem value="cost">Total Spent</SelectItem>
138139
</SelectContent>
139140
</Select>
140141
</div>
@@ -204,29 +205,52 @@ const AgentStorePage = () => {
204205
<CardContent className="pt-0">
205206
<p className="text-sm text-muted-foreground mb-4 line-clamp-2">
206207
{agent.description}
207-
</p>{' '}
208-
{/* Usage Stats */}
208+
</p>
209+
210+
{/* Primary Metric - Weekly Usage */}
211+
<div className="mb-4 p-3 bg-gradient-to-r from-green-50 to-emerald-50 dark:from-green-950/20 dark:to-emerald-950/20 rounded-lg border">
212+
<div className="flex items-center justify-between">
213+
<div className="flex items-center gap-2">
214+
<TrendingUp className="h-4 w-4 text-green-600" />
215+
<span className="text-sm font-medium text-green-700 dark:text-green-400">
216+
Weekly Usage
217+
</span>
218+
</div>
219+
<div className="text-right">
220+
<div className="text-lg font-bold text-green-700 dark:text-green-400">
221+
{formatCurrency(agent.weekly_spent)}
222+
</div>
223+
<div className="text-xs text-green-600 dark:text-green-500">
224+
{formatUsageCount(agent.usage_count)} runs
225+
</div>
226+
</div>
227+
</div>
228+
</div>
229+
230+
{/* Secondary Stats */}
209231
<div className="grid grid-cols-2 gap-3 mb-4 text-xs">
210232
<div className="flex items-center gap-1">
211-
<Users className="h-3 w-3 text-blue-500" />
233+
<Clock className="h-3 w-3 text-orange-500" />
212234
<span className="font-medium">
213-
{formatUsageCount(agent.usage_count)}
235+
{formatCurrency(agent.avg_cost_per_invocation)}
236+
</span>
237+
<span className="text-muted-foreground">
238+
avg cost
214239
</span>
215-
<span className="text-muted-foreground">uses</span>
216240
</div>
217241
<div className="flex items-center gap-1">
218-
<Star className="h-3 w-3 text-green-500" />
219-
<span className="font-medium text-green-600">
242+
<Star className="h-3 w-3 text-purple-500" />
243+
<span className="font-medium">
220244
{formatCurrency(agent.total_spent)}
221245
</span>
222-
<span className="text-muted-foreground">spent</span>
246+
<span className="text-muted-foreground">total</span>
223247
</div>
224248
<div className="flex items-center gap-1">
225-
<Clock className="h-3 w-3 text-orange-500" />
249+
<Users className="h-3 w-3 text-blue-500" />
226250
<span className="font-medium">
227-
{formatCurrency(agent.avg_cost_per_invocation)}
251+
{agent.unique_users || 0}
228252
</span>
229-
<span className="text-muted-foreground">per use</span>
253+
<span className="text-muted-foreground">users</span>
230254
</div>
231255
<div className="flex items-center gap-1">
232256
<Badge
@@ -236,6 +260,14 @@ const AgentStorePage = () => {
236260
v{agent.version}
237261
</Badge>
238262
</div>
263+
{agent.last_used && (
264+
<div className="flex items-center gap-1 col-span-2">
265+
<span className="text-xs text-muted-foreground">
266+
Last:{' '}
267+
{new Date(agent.last_used).toLocaleDateString()}
268+
</span>
269+
</div>
270+
)}
239271
</div>
240272
{/* Tags */}
241273
{agent.tags && agent.tags.length > 0 && (

web/src/app/api/agents/route.ts

Lines changed: 123 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import db from '@codebuff/common/db'
22
import * as schema from '@codebuff/common/db/schema'
3-
import { sql } from 'drizzle-orm'
3+
import { sql, eq, and, gte } from 'drizzle-orm'
44
import { NextResponse } from 'next/server'
55

66
import { logger } from '@/util/logger'
77

88
export async function GET() {
99
try {
10-
// Get all published agents with their publisher info
10+
const oneWeekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)
11+
12+
// Get all published agents with their publisher info and real usage metrics
1113
const agents = await db
1214
.select({
1315
id: schema.agentConfig.id,
@@ -25,31 +27,128 @@ export async function GET() {
2527
schema.publisher,
2628
sql`${schema.agentConfig.publisher_id} = ${schema.publisher.id}`
2729
)
28-
.orderBy(sql`${schema.agentConfig.created_at} DESC`) // Sort by date descending
29-
.limit(100) // Limit for performance
30+
.orderBy(sql`${schema.agentConfig.created_at} DESC`)
31+
32+
// Get all-time usage metrics for all agents
33+
const usageMetrics = await db
34+
.select({
35+
agent_id: schema.agentRun.agent_id,
36+
publisher_id: schema.agentRun.publisher_id,
37+
agent_name: schema.agentRun.agent_name,
38+
agent_version: schema.agentRun.agent_version,
39+
total_invocations: sql<number>`COUNT(*)`,
40+
total_dollars: sql<number>`COALESCE(SUM(${schema.agentRun.total_credits}) / 100.0, 0)`,
41+
avg_cost_per_run: sql<number>`COALESCE(AVG(${schema.agentRun.total_credits}) / 100.0, 0)`,
42+
unique_users: sql<number>`COUNT(DISTINCT ${schema.agentRun.user_id})`,
43+
last_used: sql<Date>`MAX(${schema.agentRun.created_at})`,
44+
})
45+
.from(schema.agentRun)
46+
.where(
47+
and(
48+
eq(schema.agentRun.status, 'completed'),
49+
sql`${schema.agentRun.agent_id} != 'test-agent'`
50+
)
51+
)
52+
.groupBy(
53+
schema.agentRun.agent_id,
54+
schema.agentRun.publisher_id,
55+
schema.agentRun.agent_name,
56+
schema.agentRun.agent_version
57+
)
58+
59+
// Get weekly usage metrics separately
60+
const weeklyMetrics = await db
61+
.select({
62+
agent_id: schema.agentRun.agent_id,
63+
publisher_id: schema.agentRun.publisher_id,
64+
agent_name: schema.agentRun.agent_name,
65+
weekly_dollars: sql<number>`COALESCE(SUM(${schema.agentRun.total_credits}) / 100.0, 0)`,
66+
})
67+
.from(schema.agentRun)
68+
.where(
69+
and(
70+
eq(schema.agentRun.status, 'completed'),
71+
gte(schema.agentRun.created_at, oneWeekAgo),
72+
sql`${schema.agentRun.agent_id} != 'test-agent'`
73+
)
74+
)
75+
.groupBy(
76+
schema.agentRun.agent_id,
77+
schema.agentRun.publisher_id,
78+
schema.agentRun.agent_name
79+
)
80+
81+
// Create weekly metrics map
82+
const weeklyMap = new Map()
83+
weeklyMetrics.forEach((metric) => {
84+
const keys = [
85+
metric.agent_id,
86+
metric.publisher_id && metric.agent_name
87+
? `${metric.publisher_id}/${metric.agent_name}`
88+
: null,
89+
].filter(Boolean)
3090

31-
// Transform the data to include parsed agent data and mock usage metrics
91+
keys.forEach((key) => {
92+
weeklyMap.set(key, Number(metric.weekly_dollars))
93+
})
94+
})
95+
96+
// Create a map of usage metrics by agent identifier
97+
const metricsMap = new Map()
98+
usageMetrics.forEach((metric) => {
99+
// Try to match by full agent_id first, then by publisher/name combination
100+
const keys = [
101+
metric.agent_id,
102+
metric.publisher_id && metric.agent_name
103+
? `${metric.publisher_id}/${metric.agent_name}`
104+
: null,
105+
].filter(Boolean)
106+
107+
keys.forEach((key) => {
108+
const existingMetric = metricsMap.get(key)
109+
if (!existingMetric || existingMetric.last_used < metric.last_used) {
110+
metricsMap.set(key, {
111+
weekly_dollars: weeklyMap.get(key) || 0,
112+
total_dollars: Number(metric.total_dollars),
113+
total_invocations: Number(metric.total_invocations),
114+
avg_cost_per_run: Number(metric.avg_cost_per_run),
115+
unique_users: Number(metric.unique_users),
116+
last_used: metric.last_used,
117+
})
118+
}
119+
})
120+
})
121+
122+
// Transform the data to include parsed agent data and real usage metrics
32123
const transformedAgents = agents.map((agent) => {
33-
const agentData = typeof agent.data === 'string' ? JSON.parse(agent.data) : agent.data
34-
35-
// Mock usage metrics (in a real app, these would come from analytics/usage tables)
36-
const mockUsageCount = Math.floor(Math.random() * 50000) + 1000
37-
const mockTotalSpent = Math.floor(Math.random() * 5000) + 100 // $100-$5100
38-
const mockAvgCostPerInvocation = mockTotalSpent / mockUsageCount
39-
const mockResponseTime = Math.floor(Math.random() * 3000) + 500 // 500-3500ms
40-
124+
const agentData =
125+
typeof agent.data === 'string' ? JSON.parse(agent.data) : agent.data
126+
const agentName = agentData.name || agent.id
127+
128+
const agentKey = `${agent.publisher.id}/${agentName}`
129+
const metrics = metricsMap.get(agentKey) ||
130+
metricsMap.get(agent.id) || {
131+
weekly_dollars: 0,
132+
total_dollars: 0,
133+
total_invocations: 0,
134+
avg_cost_per_run: 0,
135+
unique_users: 0,
136+
last_used: null,
137+
}
138+
41139
return {
42140
id: agent.id,
43-
name: agentData.name || agent.id,
141+
name: agentName,
44142
description: agentData.description,
45143
publisher: agent.publisher,
46144
version: agent.version,
47145
created_at: agent.created_at,
48-
usage_count: mockUsageCount,
49-
total_spent: mockTotalSpent,
50-
avg_cost_per_invocation: mockAvgCostPerInvocation,
51-
avg_response_time: mockResponseTime,
52-
146+
usage_count: metrics.total_invocations,
147+
weekly_spent: metrics.weekly_dollars,
148+
total_spent: metrics.total_dollars,
149+
avg_cost_per_invocation: metrics.avg_cost_per_run,
150+
unique_users: metrics.unique_users,
151+
last_used: metrics.last_used,
53152
tags: agentData.tags || [],
54153
}
55154
})
@@ -58,13 +157,16 @@ export async function GET() {
58157
const latestAgents = new Map()
59158
transformedAgents.forEach((agent) => {
60159
const key = `${agent.publisher.id}/${agent.name}`
61-
if (!latestAgents.has(key)) { // Since it's sorted, the first one is the latest
160+
if (!latestAgents.has(key)) {
62161
latestAgents.set(key, agent)
63162
}
64163
})
65164

66165
const result = Array.from(latestAgents.values())
67166

167+
// Sort by weekly usage (most prominent metric)
168+
result.sort((a, b) => (b.weekly_spent || 0) - (a.weekly_spent || 0))
169+
68170
return NextResponse.json(result)
69171
} catch (error) {
70172
logger.error({ error }, 'Error fetching agents')
@@ -73,4 +175,4 @@ export async function GET() {
73175
{ status: 500 }
74176
)
75177
}
76-
}
178+
}

0 commit comments

Comments
 (0)