Files
vibn-frontend/app/[workspace]/project/[projectId]/audit/page.tsx

957 lines
40 KiB
TypeScript

'use client';
import { use, useState } from 'react';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Separator } from '@/components/ui/separator';
import { Loader2, FileText, TrendingUp, DollarSign, Code, Calendar, Clock } from 'lucide-react';
interface AuditReport {
projectId: string;
generatedAt: string;
timeline: {
firstActivity: string | null;
lastActivity: string | null;
totalDays: number;
activeDays: number;
totalSessions: number;
sessions: Array<{
sessionId: string;
date: string;
startTime: string;
endTime: string;
duration: number;
messageCount: number;
userMessages: number;
aiMessages: number;
topics: string[];
filesWorkedOn: string[];
}>;
velocity: {
messagesPerDay: number;
averageSessionLength: number;
peakProductivityHours: number[];
};
};
costs: {
messageStats: {
totalMessages: number;
userMessages: number;
aiMessages: number;
avgMessageLength: number;
};
estimatedTokens: {
input: number;
output: number;
total: number;
};
costs: {
inputCost: number;
outputCost: number;
totalCost: number;
currency: string;
};
model: string;
pricing: {
inputPer1M: number;
outputPer1M: number;
};
};
features: Array<{
name: string;
description: string;
pages: string[];
apis: string[];
status: string;
}>;
techStack: {
frontend: Record<string, string>;
backend: Record<string, string>;
integrations: string[];
};
extensionActivity: {
totalSessions: number;
uniqueFilesEdited: number;
topFiles: Array<{ file: string; editCount: number }>;
earliestActivity: string | null;
latestActivity: string | null;
} | null;
gitHistory: {
totalCommits: number;
firstCommit: string | null;
lastCommit: string | null;
totalFilesChanged: number;
totalInsertions: number;
totalDeletions: number;
commits: Array<{
hash: string;
date: string;
author: string;
message: string;
filesChanged: number;
insertions: number;
deletions: number;
}>;
topFiles: Array<{ filePath: string; changeCount: number }>;
commitsByDay: Record<string, number>;
authors: Array<{ name: string; commitCount: number }>;
} | null;
unifiedTimeline: {
projectId: string;
dateRange: {
earliest: string;
latest: string;
totalDays: number;
};
days: Array<{
date: string;
dayOfWeek: string;
gitCommits: any[];
extensionSessions: any[];
cursorMessages: any[];
summary: {
totalGitCommits: number;
totalExtensionSessions: number;
totalCursorMessages: number;
linesAdded: number;
linesRemoved: number;
uniqueFilesModified: number;
};
}>;
dataSources: {
git: { available: boolean; firstDate: string | null; lastDate: string | null; totalRecords: number };
extension: { available: boolean; firstDate: string | null; lastDate: string | null; totalRecords: number };
cursor: { available: boolean; firstDate: string | null; lastDate: string | null; totalRecords: number };
};
} | null;
summary: {
totalConversations: number;
totalMessages: number;
developmentPeriod: number;
estimatedCost: number;
extensionSessions: number;
filesEdited: number;
gitCommits: number;
linesAdded: number;
linesRemoved: number;
timelineDays: number;
};
}
export default function ProjectAuditPage({
params,
}: {
params: Promise<{ workspace: string; projectId: string }>;
}) {
const { workspace, projectId } = use(params);
const [report, setReport] = useState<AuditReport | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const generateReport = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(`/api/projects/${projectId}/audit/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({})
});
if (!response.ok) {
const data = await response.json();
throw new Error(data.error || 'Failed to generate report');
}
const data = await response.json();
setReport(data);
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
} finally {
setLoading(false);
}
};
const formatDate = (dateStr: string | null) => {
if (!dateStr) return 'N/A';
return new Date(dateStr).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
};
const formatCurrency = (amount: number) => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(amount);
};
const formatNumber = (num: number) => {
return new Intl.NumberFormat('en-US').format(num);
};
return (
<div className="container mx-auto py-8 space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold tracking-tight">Project Audit Report</h1>
<p className="text-muted-foreground mt-2">
Comprehensive analysis of development history, costs, and architecture
</p>
</div>
<Button onClick={generateReport} disabled={loading}>
{loading ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Generating...
</>
) : (
<>
<FileText className="mr-2 h-4 w-4" />
Generate Report
</>
)}
</Button>
</div>
{error && (
<Card className="border-destructive">
<CardHeader>
<CardTitle className="text-destructive">Error</CardTitle>
</CardHeader>
<CardContent>
<p>{error}</p>
{error.includes('No conversations found') && (
<p className="mt-2 text-sm text-muted-foreground">
Import Cursor conversations first to generate an audit report.
</p>
)}
</CardContent>
</Card>
)}
{!report && !loading && !error && (
<Card>
<CardHeader>
<CardTitle>Ready to Generate</CardTitle>
<CardDescription>
Click the button above to analyze your project's development history,
calculate costs, and document your architecture.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid gap-4 md:grid-cols-3">
<div className="flex items-center space-x-3">
<Calendar className="h-8 w-8 text-muted-foreground" />
<div>
<p className="font-semibold">Timeline Analysis</p>
<p className="text-sm text-muted-foreground">Work sessions & velocity</p>
</div>
</div>
<div className="flex items-center space-x-3">
<DollarSign className="h-8 w-8 text-muted-foreground" />
<div>
<p className="font-semibold">Cost Estimation</p>
<p className="text-sm text-muted-foreground">AI & developer costs</p>
</div>
</div>
<div className="flex items-center space-x-3">
<Code className="h-8 w-8 text-muted-foreground" />
<div>
<p className="font-semibold">Architecture</p>
<p className="text-sm text-muted-foreground">Features & tech stack</p>
</div>
</div>
</div>
</CardContent>
</Card>
)}
{report && (
<div className="space-y-6">
{/* Summary Section */}
<div className="grid gap-4 md:grid-cols-3 lg:grid-cols-6">
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-sm font-medium text-muted-foreground">
Total Messages
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{formatNumber(report.summary.totalMessages)}</div>
<p className="text-xs text-muted-foreground mt-1">
{report.summary.totalConversations} conversations
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-sm font-medium text-muted-foreground">
Development Period
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{report.summary.developmentPeriod} days</div>
<p className="text-xs text-muted-foreground mt-1">
{report.timeline.activeDays} active days
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-sm font-medium text-muted-foreground">
Work Sessions
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{report.timeline.totalSessions}</div>
<p className="text-xs text-muted-foreground mt-1">
Avg {report.timeline.velocity.averageSessionLength} min
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-sm font-medium text-muted-foreground">
AI Cost
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{formatCurrency(report.summary.estimatedCost)}</div>
<p className="text-xs text-muted-foreground mt-1">
{report.costs.model}
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-sm font-medium text-muted-foreground">
Git Commits
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{formatNumber(report.summary.gitCommits)}</div>
<p className="text-xs text-muted-foreground mt-1">
Code changes
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-sm font-medium text-muted-foreground">
Lines Changed
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-lg font-bold">
<span className="text-green-600">+{formatNumber(report.summary.linesAdded)}</span>
{' / '}
<span className="text-red-600">-{formatNumber(report.summary.linesRemoved)}</span>
</div>
<p className="text-xs text-muted-foreground mt-1">
Total modifications
</p>
</CardContent>
</Card>
</div>
{/* Unified Timeline Section */}
{report.unifiedTimeline && (
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<Calendar className="mr-2 h-5 w-5" />
Complete Project Timeline
</CardTitle>
<CardDescription>
Day-by-day history combining Git commits, Extension activity, and Cursor messages
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{/* Data Source Overview */}
<div className="grid gap-4 md:grid-cols-3 mb-6">
<div className={`border rounded-lg p-3 ${report.unifiedTimeline.dataSources.git.available ? 'bg-green-50 border-green-200' : 'bg-gray-50'}`}>
<p className="text-sm font-medium mb-1">📊 Git Commits</p>
<p className="text-xs text-muted-foreground">
{report.unifiedTimeline.dataSources.git.available ? (
<>
{report.unifiedTimeline.dataSources.git.totalRecords} commits<br/>
{formatDate(report.unifiedTimeline.dataSources.git.firstDate)} to {formatDate(report.unifiedTimeline.dataSources.git.lastDate)}
</>
) : 'No data'}
</p>
</div>
<div className={`border rounded-lg p-3 ${report.unifiedTimeline.dataSources.extension.available ? 'bg-blue-50 border-blue-200' : 'bg-gray-50'}`}>
<p className="text-sm font-medium mb-1">💻 Extension Activity</p>
<p className="text-xs text-muted-foreground">
{report.unifiedTimeline.dataSources.extension.available ? (
<>
{report.unifiedTimeline.dataSources.extension.totalRecords} sessions<br/>
{formatDate(report.unifiedTimeline.dataSources.extension.firstDate)} to {formatDate(report.unifiedTimeline.dataSources.extension.lastDate)}
</>
) : 'No data'}
</p>
</div>
<div className={`border rounded-lg p-3 ${report.unifiedTimeline.dataSources.cursor.available ? 'bg-purple-50 border-purple-200' : 'bg-gray-50'}`}>
<p className="text-sm font-medium mb-1">🤖 Cursor Messages</p>
<p className="text-xs text-muted-foreground">
{report.unifiedTimeline.dataSources.cursor.available ? (
<>
{report.unifiedTimeline.dataSources.cursor.totalRecords} messages<br/>
{formatDate(report.unifiedTimeline.dataSources.cursor.firstDate)} to {formatDate(report.unifiedTimeline.dataSources.cursor.lastDate)}
</>
) : 'No data'}
</p>
</div>
</div>
<Separator />
{/* Timeline Days */}
<div className="space-y-3 max-h-[600px] overflow-y-auto">
{report.unifiedTimeline.days.filter(day =>
day.summary.totalGitCommits > 0 ||
day.summary.totalExtensionSessions > 0 ||
day.summary.totalCursorMessages > 0
).reverse().map((day, index) => (
<div key={index} className="border-l-4 border-primary/30 pl-4 py-3 hover:bg-accent/50 rounded-r-lg transition-colors">
<div className="flex items-start justify-between mb-2">
<div>
<h4 className="font-semibold">{formatDate(day.date)}</h4>
<p className="text-xs text-muted-foreground">{day.dayOfWeek}</p>
</div>
<div className="flex gap-2 text-xs">
{day.summary.totalGitCommits > 0 && (
<span className="px-2 py-1 bg-green-100 text-green-800 rounded">
📊 {day.summary.totalGitCommits}
</span>
)}
{day.summary.totalExtensionSessions > 0 && (
<span className="px-2 py-1 bg-blue-100 text-blue-800 rounded">
💻 {day.summary.totalExtensionSessions}
</span>
)}
{day.summary.totalCursorMessages > 0 && (
<span className="px-2 py-1 bg-purple-100 text-purple-800 rounded">
🤖 {day.summary.totalCursorMessages}
</span>
)}
</div>
</div>
<div className="space-y-2 text-sm">
{/* Git Commits */}
{day.gitCommits.length > 0 && (
<div className="bg-green-50 rounded p-2">
<p className="text-xs font-medium text-green-900 mb-1">Git Commits:</p>
{day.gitCommits.map((commit: any, idx: number) => (
<div key={idx} className="text-xs text-green-800 ml-2">
• {commit.message}
<span className="text-green-600 ml-1">
(+{commit.insertions}/-{commit.deletions})
</span>
</div>
))}
</div>
)}
{/* Extension Sessions */}
{day.extensionSessions.length > 0 && (
<div className="bg-blue-50 rounded p-2">
<p className="text-xs font-medium text-blue-900 mb-1">
Extension Sessions: {day.summary.totalExtensionSessions}
({day.summary.uniqueFilesModified} files modified)
</p>
{day.extensionSessions.slice(0, 3).map((session: any, idx: number) => (
<div key={idx} className="text-xs text-blue-800 ml-2">
• {session.duration} min session
{session.conversationSummary && (
<span className="ml-1">- {session.conversationSummary.substring(0, 50)}...</span>
)}
</div>
))}
{day.extensionSessions.length > 3 && (
<p className="text-xs text-blue-600 ml-2 mt-1">
+{day.extensionSessions.length - 3} more sessions
</p>
)}
</div>
)}
{/* Cursor Messages */}
{day.cursorMessages.length > 0 && (
<div className="bg-purple-50 rounded p-2">
<p className="text-xs font-medium text-purple-900 mb-1">
AI Conversations: {day.summary.totalCursorMessages} messages
</p>
<div className="text-xs text-purple-800 ml-2">
• Active in: {[...new Set(day.cursorMessages.map((m: any) => m.conversationName))].join(', ')}
</div>
</div>
)}
</div>
{/* Day Summary */}
{(day.summary.linesAdded > 0 || day.summary.linesRemoved > 0) && (
<div className="mt-2 pt-2 border-t text-xs text-muted-foreground">
Total changes: <span className="text-green-600">+{day.summary.linesAdded}</span> /
<span className="text-red-600"> -{day.summary.linesRemoved}</span> lines
</div>
)}
</div>
))}
</div>
</CardContent>
</Card>
)}
{/* Timeline Section */}
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<Calendar className="mr-2 h-5 w-5" />
Development Timeline
</CardTitle>
<CardDescription>
Work sessions and development velocity
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid gap-4 md:grid-cols-2">
<div>
<p className="text-sm font-medium mb-1">Development Period</p>
<p className="text-2xl font-bold">{formatDate(report.timeline.firstActivity)}</p>
<p className="text-sm text-muted-foreground">to {formatDate(report.timeline.lastActivity)}</p>
</div>
<div>
<p className="text-sm font-medium mb-1">Peak Productivity Hours</p>
<p className="text-2xl font-bold">
{report.timeline.velocity.peakProductivityHours.map(h => `${h}:00`).join(', ')}
</p>
<p className="text-sm text-muted-foreground">Most active times</p>
</div>
</div>
<Separator />
<div>
<p className="text-sm font-medium mb-3">Velocity Metrics</p>
<div className="grid gap-2 text-sm">
<div className="flex justify-between">
<span className="text-muted-foreground">Messages per day:</span>
<span className="font-mono">{report.timeline.velocity.messagesPerDay.toFixed(1)}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Average session length:</span>
<span className="font-mono">{report.timeline.velocity.averageSessionLength} minutes</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Total sessions:</span>
<span className="font-mono">{report.timeline.totalSessions}</span>
</div>
</div>
</div>
<Separator />
<div>
<p className="text-sm font-medium mb-3">Recent Sessions</p>
<div className="space-y-2">
{report.timeline.sessions.slice(-5).reverse().map((session) => (
<div key={session.sessionId} className="border rounded-lg p-3 text-sm">
<div className="flex items-center justify-between mb-2">
<span className="font-medium">{formatDate(session.date)}</span>
<span className="text-muted-foreground font-mono">
<Clock className="inline h-3 w-3 mr-1" />
{session.duration} min
</span>
</div>
<div className="text-xs text-muted-foreground">
{session.messageCount} messages • {session.topics.slice(0, 2).join(', ')}
</div>
</div>
))}
</div>
</div>
</CardContent>
</Card>
{/* Extension Activity Section */}
{report.extensionActivity && (
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<Code className="mr-2 h-5 w-5" />
File Edit Activity
</CardTitle>
<CardDescription>
Files you've edited tracked by the Cursor Monitor extension
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid gap-4 md:grid-cols-3">
<div>
<p className="text-sm font-medium mb-1">Extension Sessions</p>
<p className="text-2xl font-bold">{report.extensionActivity.totalSessions}</p>
<p className="text-xs text-muted-foreground mt-1">Work sessions logged</p>
</div>
<div>
<p className="text-sm font-medium mb-1">Files Edited</p>
<p className="text-2xl font-bold">{report.extensionActivity.uniqueFilesEdited}</p>
<p className="text-xs text-muted-foreground mt-1">Unique files modified</p>
</div>
<div>
<p className="text-sm font-medium mb-1">Activity Period</p>
<p className="text-sm font-bold">
{report.extensionActivity.earliestActivity
? formatDate(report.extensionActivity.earliestActivity)
: 'N/A'}
</p>
<p className="text-xs text-muted-foreground mt-1">
to {report.extensionActivity.latestActivity
? formatDate(report.extensionActivity.latestActivity)
: 'N/A'}
</p>
</div>
</div>
<Separator />
<div>
<p className="text-sm font-medium mb-3">Most Edited Files (Top 20)</p>
<div className="space-y-2 max-h-96 overflow-y-auto">
{report.extensionActivity.topFiles.map((item, index) => (
<div key={index} className="flex items-center justify-between border-b pb-2">
<span className="text-sm font-mono truncate flex-1" title={item.file}>
{item.file.split('/').pop()}
</span>
<span className="text-xs text-muted-foreground ml-2">
{item.editCount} {item.editCount === 1 ? 'edit' : 'edits'}
</span>
</div>
))}
</div>
</div>
</CardContent>
</Card>
)}
{/* Git Commit History Section */}
{report.gitHistory && (
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<FileText className="mr-2 h-5 w-5" />
Git Commit History
</CardTitle>
<CardDescription>
Complete development history from Git repository
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid gap-4 md:grid-cols-3">
<div>
<p className="text-sm font-medium mb-1">Total Commits</p>
<p className="text-2xl font-bold">{report.gitHistory.totalCommits}</p>
<p className="text-xs text-muted-foreground mt-1">Code changes tracked</p>
</div>
<div>
<p className="text-sm font-medium mb-1">Lines of Code</p>
<p className="text-2xl font-bold text-green-600">
+{formatNumber(report.gitHistory.totalInsertions)}
</p>
<p className="text-2xl font-bold text-red-600">
-{formatNumber(report.gitHistory.totalDeletions)}
</p>
</div>
<div>
<p className="text-sm font-medium mb-1">Repository Period</p>
<p className="text-sm font-bold">
{report.gitHistory.firstCommit
? formatDate(report.gitHistory.firstCommit)
: 'N/A'}
</p>
<p className="text-xs text-muted-foreground mt-1">
to {report.gitHistory.lastCommit
? formatDate(report.gitHistory.lastCommit)
: 'N/A'}
</p>
</div>
</div>
<Separator />
{/* Authors */}
{report.gitHistory.authors.length > 0 && (
<>
<div>
<p className="text-sm font-medium mb-3">Contributors</p>
<div className="flex flex-wrap gap-2">
{report.gitHistory.authors.map((author, index) => (
<span key={index} className="text-xs px-3 py-1 bg-secondary rounded-full">
{author.name} ({author.commitCount} {author.commitCount === 1 ? 'commit' : 'commits'})
</span>
))}
</div>
</div>
<Separator />
</>
)}
{/* Top Files */}
<div>
<p className="text-sm font-medium mb-3">Most Changed Files (Top 20)</p>
<div className="space-y-2 max-h-96 overflow-y-auto">
{report.gitHistory.topFiles.map((item, index) => (
<div key={index} className="flex items-center justify-between border-b pb-2">
<span className="text-sm font-mono truncate flex-1" title={item.filePath}>
{item.filePath.split('/').pop()}
</span>
<span className="text-xs text-muted-foreground ml-2">
{item.changeCount} {item.changeCount === 1 ? 'change' : 'changes'}
</span>
</div>
))}
</div>
</div>
<Separator />
{/* Recent Commits */}
<div>
<p className="text-sm font-medium mb-3">Recent Commits (Last 20)</p>
<div className="space-y-3 max-h-96 overflow-y-auto">
{report.gitHistory.commits.slice(0, 20).map((commit, index) => (
<div key={index} className="border-l-2 border-primary/20 pl-3 py-1">
<div className="flex items-start justify-between gap-2">
<div className="flex-1 min-w-0">
<p className="text-sm font-medium truncate">{commit.message}</p>
<p className="text-xs text-muted-foreground mt-1">
{commit.author} {formatDate(commit.date)}
<span className="font-mono ml-1">{commit.hash}</span>
</p>
</div>
<div className="text-xs text-muted-foreground whitespace-nowrap">
<span className="text-green-600">+{commit.insertions}</span> /
<span className="text-red-600">-{commit.deletions}</span>
</div>
</div>
</div>
))}
</div>
</div>
</CardContent>
</Card>
)}
{/* Cost Analysis Section */}
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<DollarSign className="mr-2 h-5 w-5" />
AI Cost Analysis
</CardTitle>
<CardDescription>
Estimated costs based on {report.costs.model} usage
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid gap-4 md:grid-cols-2">
<div>
<p className="text-sm font-medium mb-3">Message Statistics</p>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-muted-foreground">Total messages:</span>
<span className="font-mono">{formatNumber(report.costs.messageStats.totalMessages)}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">User messages:</span>
<span className="font-mono">{formatNumber(report.costs.messageStats.userMessages)}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">AI messages:</span>
<span className="font-mono">{formatNumber(report.costs.messageStats.aiMessages)}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Avg length:</span>
<span className="font-mono">{report.costs.messageStats.avgMessageLength} chars</span>
</div>
</div>
</div>
<div>
<p className="text-sm font-medium mb-3">Token Usage</p>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-muted-foreground">Input tokens:</span>
<span className="font-mono">{formatNumber(report.costs.estimatedTokens.input)}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Output tokens:</span>
<span className="font-mono">{formatNumber(report.costs.estimatedTokens.output)}</span>
</div>
<Separator className="my-2" />
<div className="flex justify-between">
<span className="text-muted-foreground">Total tokens:</span>
<span className="font-mono">{formatNumber(report.costs.estimatedTokens.total)}</span>
</div>
</div>
</div>
</div>
<Separator />
<div>
<p className="text-sm font-medium mb-3">Cost Breakdown</p>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-muted-foreground">
Input cost ({formatCurrency(report.costs.pricing.inputPer1M)}/1M tokens):
</span>
<span className="font-mono">{formatCurrency(report.costs.costs.inputCost)}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">
Output cost ({formatCurrency(report.costs.pricing.outputPer1M)}/1M tokens):
</span>
<span className="font-mono">{formatCurrency(report.costs.costs.outputCost)}</span>
</div>
</div>
</div>
<Separator />
<div className="bg-primary/5 rounded-lg p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium">Total AI Cost</p>
<p className="text-xs text-muted-foreground mt-1">{report.costs.model}</p>
</div>
<div className="text-3xl font-bold">{formatCurrency(report.costs.costs.totalCost)}</div>
</div>
</div>
<div className="text-xs text-muted-foreground">
<p>* Token estimation: ~4 characters per token</p>
<p className="mt-1">* Costs are estimates based on message content length</p>
</div>
</CardContent>
</Card>
{/* Features Section */}
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<Code className="mr-2 h-5 w-5" />
Features Implemented
</CardTitle>
<CardDescription>
Current project capabilities and status
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
{report.features.map((feature, index) => (
<div key={index} className="border rounded-lg p-4">
<div className="flex items-center justify-between mb-2">
<h3 className="font-semibold">{feature.name}</h3>
<span className={`text-xs px-2 py-1 rounded-full ${
feature.status === 'complete' ? 'bg-green-100 text-green-800' :
feature.status === 'in-progress' ? 'bg-yellow-100 text-yellow-800' :
'bg-gray-100 text-gray-800'
}`}>
{feature.status}
</span>
</div>
<p className="text-sm text-muted-foreground mb-3">{feature.description}</p>
<div className="grid gap-2 text-xs">
{feature.pages.length > 0 && (
<div>
<span className="font-medium">Pages:</span>{' '}
<span className="text-muted-foreground">{feature.pages.join(', ')}</span>
</div>
)}
{feature.apis.length > 0 && (
<div>
<span className="font-medium">APIs:</span>{' '}
<span className="text-muted-foreground font-mono">{feature.apis.join(', ')}</span>
</div>
)}
</div>
</div>
))}
</div>
</CardContent>
</Card>
{/* Tech Stack Section */}
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<TrendingUp className="mr-2 h-5 w-5" />
Technology Stack
</CardTitle>
<CardDescription>
Frameworks, libraries, and integrations
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div>
<p className="text-sm font-medium mb-2">Frontend</p>
<div className="grid gap-2 text-sm">
{Object.entries(report.techStack.frontend).map(([key, value]) => (
<div key={key} className="flex justify-between">
<span className="text-muted-foreground capitalize">{key.replace(/([A-Z])/g, ' $1')}:</span>
<span className="font-mono">{value}</span>
</div>
))}
</div>
</div>
<Separator />
<div>
<p className="text-sm font-medium mb-2">Backend</p>
<div className="grid gap-2 text-sm">
{Object.entries(report.techStack.backend).map(([key, value]) => (
<div key={key} className="flex justify-between">
<span className="text-muted-foreground capitalize">{key}:</span>
<span className="font-mono">{value}</span>
</div>
))}
</div>
</div>
<Separator />
<div>
<p className="text-sm font-medium mb-2">Integrations</p>
<div className="flex flex-wrap gap-2">
{report.techStack.integrations.map((integration) => (
<span key={integration} className="text-xs px-2 py-1 bg-secondary rounded-md">
{integration}
</span>
))}
</div>
</div>
</CardContent>
</Card>
<div className="text-xs text-muted-foreground text-center">
Report generated at {new Date(report.generatedAt).toLocaleString()}
</div>
</div>
)}
</div>
);
}