add ai pages, cc usage

This commit is contained in:
Aidan 2025-08-22 01:28:36 -04:00
parent 2f5b86111c
commit 7a9f907661
11 changed files with 1204 additions and 1 deletions

337
app/ai/claude/page.tsx Normal file
View file

@ -0,0 +1,337 @@
"use client"
import Header from '@/components/Header'
import Footer from '@/components/Footer'
import { useState, useEffect } from 'react'
import { SiClaude } from 'react-icons/si'
import {
Line,
BarChart,
Bar,
PieChart,
Pie,
Cell,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend,
ResponsiveContainer,
Area,
AreaChart,
ComposedChart,
} from 'recharts'
interface ModelBreakdown {
modelName: string
inputTokens: number
outputTokens: number
cacheCreationTokens: number
cacheReadTokens: number
cost: number
}
interface DailyData {
date: string
inputTokens: number
outputTokens: number
cacheCreationTokens: number
cacheReadTokens: number
totalTokens: number
totalCost: number
modelsUsed: string[]
modelBreakdowns: ModelBreakdown[]
}
interface CCData {
daily: DailyData[]
totals: {
inputTokens: number
outputTokens: number
cacheCreationTokens: number
cacheReadTokens: number
totalCost: number
totalTokens: number
}
}
const COLORS = ['#c15f3c', '#b1ada1', '#f4f3ee', '#c15f3c', '#b1ada1', '#f4f3ee']
export default function AI() {
const [data, setData] = useState<CCData | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [selectedMetric, setSelectedMetric] = useState<'cost' | 'tokens'>('cost')
useEffect(() => {
fetch('/data/cc.json')
.then(res => {
if (!res.ok) throw new Error('Failed to fetch data')
return res.json()
})
.then(data => {
setData(data)
setLoading(false)
})
.catch(err => {
setError(err.message)
setLoading(false)
})
}, [])
if (loading) {
return (
<div className="min-h-screen flex flex-col">
<Header />
<main className="flex-1 flex items-center justify-center">
<div className="text-gray-300">Loading Claude metrics...</div>
</main>
<Footer />
</div>
)
}
if (error || !data) {
return (
<div className="min-h-screen flex flex-col">
<Header />
<main className="flex-1 flex items-center justify-center">
<div className="text-red-400">Error loading data: {error}</div>
</main>
<Footer />
</div>
)
}
const modelUsageData = data.daily.reduce((acc, day) => {
day.modelBreakdowns.forEach(model => {
const existing = acc.find(m => m.name === model.modelName)
if (existing) {
existing.value += model.cost
} else {
acc.push({ name: model.modelName, value: model.cost })
}
})
return acc
}, [] as { name: string; value: number }[])
const tokenTypeData = [
{ name: 'Input', value: data.totals.inputTokens },
{ name: 'Output', value: data.totals.outputTokens },
{ name: 'Cache Creation', value: data.totals.cacheCreationTokens },
{ name: 'Cache Read', value: data.totals.cacheReadTokens },
]
const dailyTrendData = data.daily.map(day => ({
date: new Date(day.date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }),
cost: day.totalCost,
tokens: day.totalTokens / 1000000,
inputTokens: day.inputTokens / 1000,
outputTokens: day.outputTokens / 1000,
cacheTokens: (day.cacheCreationTokens + day.cacheReadTokens) / 1000000,
}))
const formatCurrency = (value: number) => `$${value.toFixed(2)}`
const formatTokens = (value: number) => `${value.toFixed(1)}M`
return (
<div className="min-h-screen flex flex-col">
<Header />
<main className="w-full">
<div className="my-12 text-center">
<div className="flex justify-center mb-6">
<SiClaude size={60} />
</div>
<h1 className="text-4xl font-bold mb-2 text-gray-100 glow">Claude Code Usage</h1>
<p className="text-gray-400">How much I use Claude Code!</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 px-4">
<div className="p-6 border-2 border-gray-700 rounded-lg hover:border-gray-600 transition-colors duration-300">
<h3 className="text-sm font-medium text-gray-400 mb-2">Total Cost</h3>
<p className="text-3xl font-bold text-[#c15f3c]">${data.totals.totalCost.toFixed(2)}</p>
</div>
<div className="p-6 border-2 border-gray-700 rounded-lg hover:border-gray-600 transition-colors duration-300">
<h3 className="text-sm font-medium text-gray-400 mb-2">Total Tokens</h3>
<p className="text-3xl font-bold text-[#c15f3c]">{(data.totals.totalTokens / 1000000).toFixed(1)}M</p>
</div>
<div className="p-6 border-2 border-gray-700 rounded-lg hover:border-gray-600 transition-colors duration-300">
<h3 className="text-sm font-medium text-gray-400 mb-2">Days Active</h3>
<p className="text-3xl font-bold text-[#c15f3c]">{data.daily.length}</p>
</div>
<div className="p-6 border-2 border-gray-700 rounded-lg hover:border-gray-600 transition-colors duration-300">
<h3 className="text-sm font-medium text-gray-400 mb-2">Avg Daily Cost</h3>
<p className="text-3xl font-bold text-[#c15f3c]">${(data.totals.totalCost / data.daily.length).toFixed(2)}</p>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 p-4">
<section className="p-8 border-2 border-gray-700 rounded-lg hover:border-gray-600 transition-colors duration-300">
<h2 className="text-2xl font-semibold mb-4 text-gray-200">Daily Usage Trend</h2>
<div className="flex gap-2 mb-4">
<button
onClick={() => setSelectedMetric('cost')}
className={`px-3 py-1 rounded ${selectedMetric === 'cost' ? 'bg-[#c15f3c] text-white' : 'bg-gray-700 text-gray-300'}`}
>
Cost
</button>
<button
onClick={() => setSelectedMetric('tokens')}
className={`px-3 py-1 rounded ${selectedMetric === 'tokens' ? 'bg-[#c15f3c] text-white' : 'bg-gray-700 text-gray-300'}`}
>
Tokens
</button>
</div>
<ResponsiveContainer width="100%" height={300}>
<AreaChart data={dailyTrendData}>
<CartesianGrid strokeDasharray="3 3" stroke="#374151" />
<XAxis dataKey="date" stroke="#9ca3af" />
<YAxis
stroke="#9ca3af"
tickFormatter={selectedMetric === 'cost' ? formatCurrency : formatTokens}
/>
<Tooltip
contentStyle={{ backgroundColor: '#1f2937', border: '1px solid #374151' }}
formatter={(value: number) => selectedMetric === 'cost' ? formatCurrency(value) : formatTokens(value)}
/>
<Area
type="monotone"
dataKey={selectedMetric === 'cost' ? 'cost' : 'tokens'}
stroke="#c15f3c"
fill="#c15f3c"
fillOpacity={0.3}
/>
</AreaChart>
</ResponsiveContainer>
</section>
<section className="p-8 border-2 border-gray-700 rounded-lg hover:border-gray-600 transition-colors duration-300">
<h2 className="text-2xl font-semibold mb-4 text-gray-200">Model Usage Distribution</h2>
<div className="grid grid-cols-1 xl:grid-cols-2 gap-4">
<ResponsiveContainer width="100%" height={300}>
<PieChart>
<Pie
data={modelUsageData}
cx="50%"
cy="50%"
innerRadius={60}
outerRadius={100}
fill="#8884d8"
paddingAngle={2}
dataKey="value"
>
{modelUsageData.map((entry, index) => (
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
))}
</Pie>
<Tooltip
contentStyle={{ backgroundColor: '#1f2937', border: '1px solid #374151', borderRadius: '8px' }}
formatter={(value: number) => formatCurrency(value)}
/>
</PieChart>
</ResponsiveContainer>
<div className="flex flex-col justify-center space-y-3">
{modelUsageData.map((model, index) => {
const percentage = ((model.value / data.totals.totalCost) * 100).toFixed(1)
return (
<div key={index} className="flex items-center justify-between">
<div className="flex items-center gap-2">
<div
className="w-3 h-3 rounded-full"
style={{ backgroundColor: COLORS[index % COLORS.length] }}
/>
<span className="text-gray-300 font-medium text-xs">{model.name}</span>
</div>
<div className="flex items-center gap-3">
<span className="text-gray-400 text-sm">{percentage}%</span>
<span className="text-gray-200 font-semibold">${model.value.toFixed(2)}</span>
</div>
</div>
)
})}
<div className="pt-3 mt-3 border-t border-gray-700">
<div className="flex justify-between items-center">
<span className="text-gray-400">Total Models Used</span>
<span className="text-gray-200 font-bold">{modelUsageData.length}</span>
</div>
<div className="flex justify-between items-center mt-2">
<span className="text-gray-400">Most Used</span>
<span className="text-gray-200 font-bold text-xs">
{modelUsageData[0]?.name}
</span>
</div>
</div>
</div>
</div>
</section>
<section className="p-8 border-2 border-gray-700 rounded-lg hover:border-gray-600 transition-colors duration-300">
<h2 className="text-2xl font-semibold mb-4 text-gray-200">Token Type Breakdown</h2>
<ResponsiveContainer width="100%" height={300}>
<BarChart data={tokenTypeData}>
<CartesianGrid strokeDasharray="3 3" stroke="#374151" />
<XAxis dataKey="name" stroke="#9ca3af" />
<YAxis stroke="#9ca3af" tickFormatter={(value) => `${(value / 1000000).toFixed(0)}M`} />
<Tooltip
contentStyle={{ backgroundColor: '#1f2937', border: '1px solid #374151' }}
formatter={(value: number) => `${(value / 1000000).toFixed(2)}M tokens`}
/>
<Bar dataKey="value" fill="#b1ada1" />
</BarChart>
</ResponsiveContainer>
</section>
<section className="p-8 border-2 border-gray-700 rounded-lg hover:border-gray-600 transition-colors duration-300">
<h2 className="text-2xl font-semibold mb-4 text-gray-200">Daily Token Composition</h2>
<ResponsiveContainer width="100%" height={300}>
<ComposedChart data={dailyTrendData}>
<CartesianGrid strokeDasharray="3 3" stroke="#374151" />
<XAxis dataKey="date" stroke="#9ca3af" />
<YAxis stroke="#9ca3af" tickFormatter={(value) => `${value}K`} />
<Tooltip
contentStyle={{ backgroundColor: '#1f2937', border: '1px solid #374151' }}
formatter={(value: number) => `${value.toFixed(1)}K tokens`}
/>
<Legend />
<Bar dataKey="inputTokens" stackId="a" fill="#c15f3c" name="Input (K)" />
<Bar dataKey="outputTokens" stackId="a" fill="#b1ada1" name="Output (K)" />
<Line type="monotone" dataKey="cacheTokens" stroke="#f4f3ee" name="Cache (M)" strokeWidth={2} />
</ComposedChart>
</ResponsiveContainer>
</section>
</div>
<div className="px-4 pb-4">
<section className="p-8 border-2 border-gray-700 rounded-lg hover:border-gray-600 transition-colors duration-300">
<h2 className="text-2xl font-semibold mb-4 text-gray-200">Recent Sessions</h2>
<div className="overflow-x-auto">
<table className="w-full text-left">
<thead>
<tr className="border-b border-gray-700">
<th className="py-2 px-4 text-gray-400">Date</th>
<th className="py-2 px-4 text-gray-400">Models Used</th>
<th className="py-2 px-4 text-gray-400">Total Tokens</th>
<th className="py-2 px-4 text-gray-400">Cost</th>
</tr>
</thead>
<tbody>
{data.daily.slice(-5).reverse().map((day, index) => (
<tr key={index} className="border-b border-gray-800 hover:bg-gray-800/50">
<td className="py-2 px-4 text-gray-300">{new Date(day.date).toLocaleDateString()}</td>
<td className="py-2 px-4 text-gray-300">
{day.modelsUsed.join(', ')}
</td>
<td className="py-2 px-4 text-gray-300">{(day.totalTokens / 1000000).toFixed(2)}M</td>
<td className="py-2 px-4 text-[#c15f3c] font-semibold">${day.totalCost.toFixed(2)}</td>
</tr>
))}
</tbody>
</table>
</div>
</section>
</div>
</main>
<Footer />
</div>
)
}

View file

@ -0,0 +1,73 @@
import { Bot } from 'lucide-react'
import Link from '@/components/objects/Link'
import type { AITool } from '../types'
interface AIStackProps {
tools: AITool[]
}
export default function AIStack({ tools }: AIStackProps) {
const getStatusColor = (status: string) => {
switch(status) {
case 'primary': return 'text-green-400 border-green-400 bg-green-400/10'
case 'active': return 'text-blue-400 border-blue-400 bg-blue-400/10'
case 'occasional': return 'text-yellow-400 border-yellow-400 bg-yellow-400/10'
default: return 'text-gray-400 border-gray-400'
}
}
const getStatusLabel = (status: string) => {
switch(status) {
case 'primary': return 'Primary'
case 'active': return 'Active Use'
case 'occasional': return 'Occasional Use'
default: return status
}
}
return (
<section className="p-8 border-2 border-gray-700 rounded-lg hover:border-gray-600 transition-colors duration-300">
<h2 className="text-2xl font-semibold mb-6 text-gray-200 flex items-center gap-2">
<Bot size={24} />
My AI Stack
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{tools.map((tool, index) => (
<div key={index} className="p-4 border border-gray-700 rounded-lg hover:border-gray-500 transition-all duration-300 flex flex-col">
<div className="flex items-start justify-between mb-3 flex-1">
<div className="flex items-center gap-3">
{tool.icon && <tool.icon className="text-2xl text-gray-300" />}
{tool.svg && (
<div className="w-6 h-6 text-gray-300 fill-current">
{tool.svg}
</div>
)}
<div>
<h3 className="font-semibold text-gray-200">{tool.name}</h3>
<p className="text-sm text-gray-400">{tool.description}</p>
</div>
</div>
</div>
<div className="flex items-center justify-between mt-auto">
<span className={`text-xs px-2 py-1 rounded-full border ${getStatusColor(tool.status)}`}>
{getStatusLabel(tool.status)}
</span>
<span className="flex flex-row items-center gap-4">
{tool.link && (
<Link href={tool.link} className="text-blue-400 hover:text-blue-300 text-sm">
View
</Link>
)}
{tool.usage && (
<Link href={tool.usage} className="text-blue-400 hover:text-blue-300 text-sm">
Usage
</Link>
)}
</span>
</div>
</div>
))}
</div>
</section>
)
}

View file

@ -0,0 +1,39 @@
import { Star } from 'lucide-react'
import type { FavoriteModel } from '../types'
interface FavoriteModelsProps {
models: FavoriteModel[]
}
export default function FavoriteModels({ models }: FavoriteModelsProps) {
return (
<section className="p-8 border-2 border-gray-700 rounded-lg hover:border-gray-600 transition-colors duration-300">
<h2 className="text-2xl font-semibold mb-6 text-gray-200 flex items-center gap-2">
<Star size={24} />
Favorite Models
</h2>
<div className="space-y-4">
{models.map((model, index) => (
<div key={index} className="p-4 bg-gray-800/50 rounded-lg">
<div className="flex justify-between items-start mb-2">
<div>
<h3 className="font-semibold text-gray-200">{model.name}</h3>
<p className="text-sm text-gray-400">{model.provider}</p>
</div>
<div className="flex gap-1">
{[...Array(5)].map((_, i) => (
<Star
key={i}
size={14}
className={i < model.rating ? 'fill-yellow-400 text-yellow-400' : 'text-gray-600'}
/>
))}
</div>
</div>
<p className="text-sm text-gray-300">{model.review}</p>
</div>
))}
</div>
</section>
)
}

View file

@ -0,0 +1,54 @@
import { MessageSquare, Star } from 'lucide-react'
import type { AIReview } from '../types'
interface ToolReviewsProps {
reviews: AIReview[]
}
export default function ToolReviews({ reviews }: ToolReviewsProps) {
return (
<section className="p-8 border-2 border-gray-700 rounded-lg hover:border-gray-600 transition-colors duration-300">
<h2 className="text-2xl font-semibold mb-6 text-gray-200 flex items-center gap-2">
<MessageSquare size={24} />
Tool Reviews
</h2>
<div className="space-y-4">
{reviews.map((review, index) => (
<div key={index} className="p-4 bg-gray-800/50 rounded-lg">
<div className="flex justify-between items-center mb-3">
<h3 className="font-semibold text-gray-200">{review.tool}</h3>
<div className="flex gap-1">
{[...Array(5)].map((_, i) => (
<Star
key={i}
size={14}
className={i < review.rating ? 'fill-yellow-400 text-yellow-400' : 'text-gray-600'}
/>
))}
</div>
</div>
<div className="grid grid-cols-2 gap-2 mb-2 text-sm">
<div>
<p className="text-green-400 font-medium mb-1">Pros:</p>
<ul className="text-gray-300 space-y-1">
{review.pros.map((pro, i) => (
<li key={i} className="text-xs"> {pro}</li>
))}
</ul>
</div>
<div>
<p className="text-red-400 font-medium mb-1">Cons:</p>
<ul className="text-gray-300 space-y-1">
{review.cons.map((con, i) => (
<li key={i} className="text-xs"> {con}</li>
))}
</ul>
</div>
</div>
<p className="text-sm text-blue-400 font-medium">{review.verdict}</p>
</div>
))}
</div>
</section>
)
}

View file

@ -0,0 +1,49 @@
import { Trophy, ChevronRight } from 'lucide-react'
import { SiClaude } from 'react-icons/si'
import Link from '@/components/objects/Link'
export default function TopPick() {
return (
<div className="px-4 mb-4">
<h2 className="text-4xl font-semibold mb-6 text-gray-200 flex items-center gap-2">
<Trophy size={32} />
Top Pick of 2025
</h2>
<div className="p-8 border-2 border-[#c15f3c] rounded-lg bg-orange-500/5">
<div className="grid md:grid-cols-2 gap-6">
<div className="flex items-center gap-4">
<SiClaude className="text-6xl text-[#c15f3c]" />
<div>
<h3 className="text-3xl font-bold text-gray-100">Claude</h3>
<p className="text-gray-400">by Anthropic</p>
<div className="flex items-center gap-2 mt-2">
<Link href="/ai/claude" className="text-blue-400 hover:text-blue-300 flex items-center gap-1">
View My Usage <ChevronRight size={16} />
</Link>
</div>
</div>
</div>
<div className="space-y-2">
<p className="text-gray-300">
Claude has become my go-to AI assistant for coding, writing, and learning very quickly.
I believe their Max 5x ($100/mo) is the best value for budget-conscious consumers like myself.
</p>
<div className='flex flex-col items-center gap-y-6 sm:flex-row sm:justify-between'>
<div className="flex gap-2 flex-wrap">
<span className="px-2 py-1 bg-gray-700 rounded text-xs text-gray-300">Claude Code</span>
<span className="px-2 py-1 bg-gray-700 rounded text-xs text-gray-300">Best Tool Calling</span>
<span className="px-2 py-1 bg-gray-700 rounded text-xs text-gray-300">High Value in Max Plan</span>
<span className="px-2 py-1 bg-gray-700 rounded text-xs text-gray-300">Quite Fast Interface</span>
</div>
<div className="flex items-center justify-end">
<span className="px-3 py-1 bg-[#c15f3c]/20 text-[#c15f3c] rounded-full text-sm font-medium">
Top Overall Pick
</span>
</div>
</div>
</div>
</div>
</div>
</div>
)
}

125
app/ai/data.tsx Normal file
View file

@ -0,0 +1,125 @@
import {
SiClaude,
SiGithubcopilot,
SiGooglegemini
} from 'react-icons/si'
import type { AITool, FavoriteModel, AIReview } from './types'
export const aiTools: AITool[] = [
{
name: "Claude Max 5x",
icon: SiClaude,
description: "My favorite model provider for general use and coding",
status: "primary",
usage: "/ai/claude",
link: "https://claude.ai/"
},
{
name: "GitHub Copilot Pro",
icon: SiGithubcopilot,
description: "Random edits when I don't want to start a Claude session",
status: "active",
link: "https://github.com/features/copilot"
},
{
name: "Gemini Pro",
icon: SiGooglegemini,
description: "Chatting, asking questions, and image generation",
status: "occasional",
link: "https://gemini.google.com/"
},
{
name: "v0 Free",
svg: <svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>v0</title><path d="M14.066 6.028v2.22h5.729q.075-.001.148.005l-5.853 5.752a2 2 0 0 1-.024-.309V8.247h-2.353v5.45c0 2.322 1.935 4.222 4.258 4.222h5.675v-2.22h-5.675q-.03 0-.059-.003l5.729-5.629q.006.082.006.166v5.465H24v-5.465a4.204 4.204 0 0 0-4.205-4.205zM0 8.245l8.28 9.266c.839.94 2.396.346 2.396-.914V8.245H8.19v5.44l-4.86-5.44Z"/></svg>,
description: "Generating boilerplate UIs",
status: "occasional",
link: "https://v0.dev/"
},
{
name: "Qwen",
svg: (
<svg viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg" className="size-8">
<path d="M174.82 108.75L155.38 75L165.64 57.75C166.46 56.31 166.46 54.53 165.64 53.09L155.38 35.84C154.86 34.91 153.87 34.33 152.78 34.33H114.88L106.14 19.03C105.62 18.1 104.63 17.52 103.54 17.52H83.3C82.21 17.52 81.22 18.1 80.7 19.03L61.26 52.77H41.02C39.93 52.77 38.94 53.35 38.42 54.28L28.16 71.53C27.34 72.97 27.34 74.75 28.16 76.19L45.52 107.5L36.78 122.8C35.96 124.24 35.96 126.02 36.78 127.46L47.04 144.71C47.56 145.64 48.55 146.22 49.64 146.22H87.54L96.28 161.52C96.8 162.45 97.79 163.03 98.88 163.03H119.12C120.21 163.03 121.2 162.45 121.72 161.52L141.16 127.78H158.52C159.61 127.78 160.6 127.2 161.12 126.27L171.38 109.02C172.2 107.58 172.2 105.8 171.38 104.36L174.82 108.75Z" fill="url(#paint0_radial)"/>
<path d="M119.12 163.03H98.88L87.54 144.71H49.64L61.26 126.39H80.7L38.42 55.29H61.26L83.3 19.03L93.56 37.35L83.3 55.29H161.58L151.32 72.54L170.76 106.28H151.32L141.16 88.34L101.18 163.03H119.12Z" fill="white"/>
<path d="M127.86 79.83H76.14L101.18 122.11L127.86 79.83Z" fill="url(#paint1_radial)"/>
<defs>
<radialGradient id="paint0_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(100 100) rotate(90) scale(100)">
<stop stopColor="#665CEE"/>
<stop offset="1" stopColor="#332E91"/>
</radialGradient>
<radialGradient id="paint1_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(100 100) rotate(90) scale(100)">
<stop stopColor="#665CEE"/>
<stop offset="1" stopColor="#332E91"/>
</radialGradient>
</defs>
</svg>
),
description: "My favorite open source LLM for chatting",
status: "occasional",
link: "https://chat.qwen.ai/"
},
]
export const favoriteModels: FavoriteModel[] = [
{
name: "Claude 4 Sonnet",
provider: "Anthropic",
review: "The perfect balance of capability, speed, and price. Perfect for development with React.",
rating: 5
},
{
name: "Claude 4.1 Opus",
provider: "Anthropic",
review: "Amazing planner, useful for Plan Mode in Claude Code. Useful in code generation too.",
rating: 5
},
{
name: "Qwen3-235B-A22B",
provider: "Alibaba",
review: "The OG thinking model. Amazing, funny, and smart for chats. Surprisingly good at coding too.",
rating: 5
},
{
name: "Gemini 2.5 Pro",
provider: "Google",
review: "Amazing for Deep Research and reasoning tasks. I hate it for coding.",
rating: 4
},
{
name: "gemma3 27B",
provider: "Google",
review: "My favorite for playing around with AI or creating a project. Easy to run locally and open weight!",
rating: 4
},
]
export const aiReviews: AIReview[] = [
{
tool: "Claude Code",
rating: 5,
pros: ["Flagship models", "High usage limits", "Exceptional Claude integration"],
cons: ["Can be slow", "High investment cost to get value"],
verdict: "Best overall for Claude lovers"
},
{
tool: "Cursor",
rating: 4,
pros: ["Works like magic", "Lots of model support", "Huge ecosystem and community"],
cons: ["Expensive", "Hype around it is dying", "Unclear/manipulative pricing"],
verdict: "Great all-rounder, slowly dying"
},
{
tool: "Trae",
rating: 4,
pros: ["Good UI/UX", "Very budget-friendly", "Fantastic premium usage limits"],
cons: ["No thinking", "Occasional parsing issues"],
verdict: "Essential for productivity"
},
{
tool: "GitHub Copilot",
rating: 3,
pros: ["Latest models", "Great autocomplete", "Budget-friendly subscription price"],
cons: ["No thinking", "Low quality output", "Bad support for other IDEs"],
verdict: "Good for casual use"
},
]

40
app/ai/page.tsx Normal file
View file

@ -0,0 +1,40 @@
"use client"
import Header from '@/components/Header'
import Footer from '@/components/Footer'
import { Brain } from 'lucide-react'
import TopPick from './components/TopPick'
import AIStack from './components/AIStack'
import FavoriteModels from './components/FavoriteModels'
import ToolReviews from './components/ToolReviews'
import { aiTools, favoriteModels, aiReviews } from './data'
export default function AI() {
return (
<div className="min-h-screen flex flex-col">
<Header />
<main className="w-full px-2 sm:px-6">
<div className="my-12 text-center">
<div className="flex justify-center mb-6">
<Brain size={60} />
</div>
<h1 className="text-4xl font-bold mb-2 text-gray-100 glow">AI</h1>
<p className="text-gray-400">My journey with using LLMs</p>
</div>
<TopPick />
<div className="p-4">
<AIStack tools={aiTools} />
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 p-4">
<FavoriteModels models={favoriteModels} />
<ToolReviews reviews={aiReviews} />
</div>
</main>
<Footer />
</div>
)
}

24
app/ai/types.ts Normal file
View file

@ -0,0 +1,24 @@
export interface AITool {
name: string;
icon?: React.ElementType;
svg?: React.ReactNode;
description: string;
status: 'primary' | 'active' | 'occasional' | string;
link?: string;
usage?: string;
}
export interface FavoriteModel {
name: string;
provider: string;
review: string;
rating: number;
}
export interface AIReview {
tool: string;
rating: number;
pros: string[];
cons: string[];
verdict: string;
}