open-prompt / src /app /api /analytics /route.ts
GitHub Action
Automated sync to Hugging Face
bcce530
import { NextRequest, NextResponse } from "next/server"
import prisma from "@/lib/prisma"
import { stackServerApp } from "@/lib/stack-server"
// GET - Get detailed analytics for a creator
export async function GET(request: NextRequest) {
try {
const user = await stackServerApp.getUser()
if (!user) {
return NextResponse.json(
{ error: "Unauthorized" },
{ status: 401 }
)
}
const { searchParams } = new URL(request.url)
const days = parseInt(searchParams.get("days") || "30")
const daysAgo = new Date()
daysAgo.setDate(daysAgo.getDate() - days)
// Get all prompts by this user
const prompts = await prisma.prompt.findMany({
where: { creatorId: user.id },
select: {
id: true,
title: true,
slug: true,
totalRuns: true,
starsCount: true,
remixesCount: true,
framework: true,
badges: true,
parentId: true,
createdAt: true,
},
orderBy: { totalRuns: "desc" },
})
// Get runs data for the period
const runs = await prisma.run.findMany({
where: {
prompt: { creatorId: user.id },
createdAt: { gte: daysAgo },
},
select: {
id: true,
model: true,
tokens: true,
cached: true,
createdAt: true,
promptId: true,
},
orderBy: { createdAt: "desc" },
})
// Get engagement metrics (errors tracked separately)
const engagementMetrics = await prisma.engagementMetric.findMany({
where: {
targetType: "prompt",
targetId: { in: prompts.map(p => p.id) },
createdAt: { gte: daysAgo },
},
select: {
action: true,
createdAt: true,
targetId: true,
}
})
// Aggregate runs by day
const runsByDay: Record<string, { runs: number; errors: number; tokens: number }> = {}
runs.forEach(run => {
const day = run.createdAt.toISOString().split("T")[0]
if (!runsByDay[day]) {
runsByDay[day] = { runs: 0, errors: 0, tokens: 0 }
}
runsByDay[day].runs += 1
runsByDay[day].tokens += run.tokens || 0
})
// Prepare daily data for chart
const dailyData = []
for (let i = days - 1; i >= 0; i--) {
const date = new Date()
date.setDate(date.getDate() - i)
const dateStr = date.toISOString().split("T")[0]
const dayData = runsByDay[dateStr] || { runs: 0, errors: 0, tokens: 0 }
dailyData.push({
date: dateStr,
runs: dayData.runs,
errors: dayData.errors,
tokens: dayData.tokens,
})
}
// Model usage stats
const modelUsage: Record<string, { runs: number; tokens: number }> = {}
runs.forEach(run => {
if (!modelUsage[run.model]) {
modelUsage[run.model] = { runs: 0, tokens: 0 }
}
modelUsage[run.model].runs += 1
modelUsage[run.model].tokens += run.tokens || 0
})
// Token usage per prompt
const tokensByPrompt: Record<string, number> = {}
runs.forEach(run => {
if (!tokensByPrompt[run.promptId]) {
tokensByPrompt[run.promptId] = 0
}
tokensByPrompt[run.promptId] += run.tokens || 0
})
// Remix traffic - find remixes of user's prompts
const remixTraffic = await prisma.prompt.findMany({
where: {
parentId: { in: prompts.map(p => p.id) },
createdAt: { gte: daysAgo },
},
select: {
id: true,
title: true,
parentId: true,
totalRuns: true,
createdAt: true,
},
orderBy: { createdAt: "desc" },
take: 20,
})
// Engagement breakdown
const engagementByAction: Record<string, number> = {}
engagementMetrics.forEach(metric => {
engagementByAction[metric.action] = (engagementByAction[metric.action] || 0) + 1
})
// Total tokens used
const totalTokens = runs.reduce((sum, run) => sum + (run.tokens || 0), 0)
const cachedRuns = runs.filter(r => r.cached).length
const cacheHitRate = runs.length > 0 ? (cachedRuns / runs.length) * 100 : 0
// Calculate totals
const totalRuns = prompts.reduce((sum, p) => sum + p.totalRuns, 0)
const totalStars = prompts.reduce((sum, p) => sum + p.starsCount, 0)
const totalRemixes = prompts.reduce((sum, p) => sum + p.remixesCount, 0)
// Error rate estimation (based on low token runs which might indicate errors)
const potentialErrors = runs.filter(r => r.tokens !== null && r.tokens < 10).length
const errorRate = runs.length > 0 ? (potentialErrors / runs.length) * 100 : 0
return NextResponse.json({
prompts: prompts.map(p => ({
...p,
tokens: tokensByPrompt[p.id] || 0,
})),
dailyData,
modelUsage,
totalTokens,
cacheHitRate,
errorRate,
remixTraffic,
engagementByAction,
totals: {
runs: totalRuns,
stars: totalStars,
remixes: totalRemixes,
prompts: prompts.length,
periodRuns: runs.length,
periodTokens: totalTokens,
},
})
} catch (error) {
console.error("Error fetching analytics:", error)
return NextResponse.json(
{ error: "Failed to fetch analytics" },
{ status: 500 }
)
}
}