open-prompt / src /components /analytics /analytics-dashboard.tsx
GitHub Action
Automated sync to Hugging Face
bcce530
"use client"
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Play, Star, GitFork, FileText, TrendingUp, Zap, BarChart3 } from "lucide-react"
import Link from "next/link"
interface AnalyticsData {
prompts: {
id: string
title: string
slug: string
totalRuns: number
starsCount: number
remixesCount: number
createdAt: Date
}[]
dailyData: { date: string; runs: number }[]
modelUsage: Record<string, number>
totalTokens: number
totals: {
runs: number
stars: number
remixes: number
prompts: number
}
}
interface AnalyticsDashboardProps {
data: AnalyticsData
}
export function AnalyticsDashboard({ data }: AnalyticsDashboardProps) {
const maxRuns = Math.max(...data.dailyData.map((d) => d.runs), 1)
return (
<div className="space-y-8">
{/* Summary Stats */}
<div className="grid sm:grid-cols-2 lg:grid-cols-4 gap-4">
<Card>
<CardContent className="pt-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-muted-foreground">Total Prompts</p>
<p className="text-3xl font-bold">{data.totals.prompts}</p>
</div>
<FileText className="h-8 w-8 text-muted-foreground/30" />
</div>
</CardContent>
</Card>
<Card>
<CardContent className="pt-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-muted-foreground">Total Runs</p>
<p className="text-3xl font-bold">{data.totals.runs.toLocaleString()}</p>
</div>
<Play className="h-8 w-8 text-muted-foreground/30" />
</div>
</CardContent>
</Card>
<Card>
<CardContent className="pt-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-muted-foreground">Total Stars</p>
<p className="text-3xl font-bold">{data.totals.stars.toLocaleString()}</p>
</div>
<Star className="h-8 w-8 text-muted-foreground/30" />
</div>
</CardContent>
</Card>
<Card>
<CardContent className="pt-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-muted-foreground">Total Remixes</p>
<p className="text-3xl font-bold">{data.totals.remixes.toLocaleString()}</p>
</div>
<GitFork className="h-8 w-8 text-muted-foreground/30" />
</div>
</CardContent>
</Card>
</div>
{/* Charts Row */}
<div className="grid lg:grid-cols-2 gap-6">
{/* Runs per Day Chart */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<TrendingUp className="h-5 w-5" />
Runs Per Day
</CardTitle>
<CardDescription>Last 30 days</CardDescription>
</CardHeader>
<CardContent>
<div className="flex items-end gap-1 h-[200px]">
{data.dailyData.map((day, i) => (
<div
key={day.date}
className="flex-1 group relative"
title={`${day.date}: ${day.runs} runs`}
>
<div
className="bg-primary/80 hover:bg-primary rounded-t transition-colors"
style={{
height: `${(day.runs / maxRuns) * 100}%`,
minHeight: day.runs > 0 ? "4px" : "1px",
}}
/>
<div className="absolute -top-8 left-1/2 -translate-x-1/2 bg-foreground text-background px-2 py-1 rounded text-xs opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap">
{day.runs} runs
</div>
</div>
))}
</div>
<div className="flex justify-between text-xs text-muted-foreground mt-2">
<span>30 days ago</span>
<span>Today</span>
</div>
</CardContent>
</Card>
{/* Model Usage */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Zap className="h-5 w-5" />
Model Usage
</CardTitle>
<CardDescription>
{data.totalTokens.toLocaleString()} total tokens used
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
{Object.entries(data.modelUsage).length > 0 ? (
Object.entries(data.modelUsage)
.sort(([, a], [, b]) => b - a)
.map(([model, count]) => {
const percentage = Math.round(
(count / Object.values(data.modelUsage).reduce((a, b) => a + b, 0)) * 100
)
return (
<div key={model}>
<div className="flex justify-between text-sm mb-1">
<span className="font-medium">{model}</span>
<span className="text-muted-foreground">
{count} runs ({percentage}%)
</span>
</div>
<div className="h-2 bg-muted rounded-full overflow-hidden">
<div
className="h-full bg-primary rounded-full transition-all"
style={{ width: `${percentage}%` }}
/>
</div>
</div>
)
})
) : (
<div className="text-center text-muted-foreground py-8">
No usage data yet
</div>
)}
</div>
</CardContent>
</Card>
</div>
{/* Top Prompts */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<BarChart3 className="h-5 w-5" />
Top Performing Prompts
</CardTitle>
<CardDescription>Sorted by total runs</CardDescription>
</CardHeader>
<CardContent>
{data.prompts.length > 0 ? (
<div className="space-y-3">
{data.prompts.slice(0, 10).map((prompt, i) => (
<Link
key={prompt.id}
href={`/p/${prompt.slug}`}
className="flex items-center gap-4 p-3 rounded-lg hover:bg-muted/50 transition-colors"
>
<span className="text-2xl font-bold text-muted-foreground w-8">
{i + 1}
</span>
<div className="flex-1 min-w-0">
<p className="font-medium truncate">{prompt.title}</p>
<div className="flex gap-4 text-xs text-muted-foreground mt-1">
<span className="flex items-center gap-1">
<Play className="h-3 w-3" />
{prompt.totalRuns.toLocaleString()} runs
</span>
<span className="flex items-center gap-1">
<Star className="h-3 w-3" />
{prompt.starsCount} stars
</span>
<span className="flex items-center gap-1">
<GitFork className="h-3 w-3" />
{prompt.remixesCount} remixes
</span>
</div>
</div>
<Badge variant="outline">{prompt.totalRuns} runs</Badge>
</Link>
))}
</div>
) : (
<div className="text-center text-muted-foreground py-12">
<FileText className="h-12 w-12 mx-auto mb-4 opacity-20" />
<p>No prompts yet</p>
<p className="text-sm mt-1">
<Link href="/create" className="text-primary hover:underline">
Create your first prompt
</Link>
</p>
</div>
)}
</CardContent>
</Card>
</div>
)
}