diff --git a/app/ai/claude/page.tsx b/app/ai/claude/page.tsx new file mode 100644 index 0000000..cafdc55 --- /dev/null +++ b/app/ai/claude/page.tsx @@ -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(null) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(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 ( +
+
+
+
Loading Claude metrics...
+
+
+
+ ) + } + + if (error || !data) { + return ( +
+
+
+
Error loading data: {error}
+
+
+
+ ) + } + + 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 ( +
+
+
+
+
+ +
+

Claude Code Usage

+

How much I use Claude Code!

+
+ +
+
+

Total Cost

+

${data.totals.totalCost.toFixed(2)}

+
+
+

Total Tokens

+

{(data.totals.totalTokens / 1000000).toFixed(1)}M

+
+
+

Days Active

+

{data.daily.length}

+
+
+

Avg Daily Cost

+

${(data.totals.totalCost / data.daily.length).toFixed(2)}

+
+
+ +
+
+

Daily Usage Trend

+
+ + +
+ + + + + + selectedMetric === 'cost' ? formatCurrency(value) : formatTokens(value)} + /> + + + +
+ +
+

Model Usage Distribution

+
+ + + + {modelUsageData.map((entry, index) => ( + + ))} + + formatCurrency(value)} + /> + + +
+ {modelUsageData.map((model, index) => { + const percentage = ((model.value / data.totals.totalCost) * 100).toFixed(1) + return ( +
+
+
+ {model.name} +
+
+ {percentage}% + ${model.value.toFixed(2)} +
+
+ ) + })} +
+
+ Total Models Used + {modelUsageData.length} +
+
+ Most Used + + {modelUsageData[0]?.name} + +
+
+
+
+
+ +
+

Token Type Breakdown

+ + + + + `${(value / 1000000).toFixed(0)}M`} /> + `${(value / 1000000).toFixed(2)}M tokens`} + /> + + + +
+ +
+

Daily Token Composition

+ + + + + `${value}K`} /> + `${value.toFixed(1)}K tokens`} + /> + + + + + + +
+
+ +
+
+

Recent Sessions

+
+ + + + + + + + + + + {data.daily.slice(-5).reverse().map((day, index) => ( + + + + + + + ))} + +
DateModels UsedTotal TokensCost
{new Date(day.date).toLocaleDateString()} + {day.modelsUsed.join(', ')} + {(day.totalTokens / 1000000).toFixed(2)}M${day.totalCost.toFixed(2)}
+
+
+
+
+
+
+ ) +} \ No newline at end of file diff --git a/app/ai/components/AIStack.tsx b/app/ai/components/AIStack.tsx new file mode 100644 index 0000000..c24cd91 --- /dev/null +++ b/app/ai/components/AIStack.tsx @@ -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 ( +
+

+ + My AI Stack +

+
+ {tools.map((tool, index) => ( +
+
+
+ {tool.icon && } + {tool.svg && ( +
+ {tool.svg} +
+ )} +
+

{tool.name}

+

{tool.description}

+
+
+
+
+ + {getStatusLabel(tool.status)} + + + {tool.link && ( + + View → + + )} + {tool.usage && ( + + Usage → + + )} + +
+
+ ))} +
+
+ ) +} \ No newline at end of file diff --git a/app/ai/components/FavoriteModels.tsx b/app/ai/components/FavoriteModels.tsx new file mode 100644 index 0000000..9212d0d --- /dev/null +++ b/app/ai/components/FavoriteModels.tsx @@ -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 ( +
+

+ + Favorite Models +

+
+ {models.map((model, index) => ( +
+
+
+

{model.name}

+

{model.provider}

+
+
+ {[...Array(5)].map((_, i) => ( + + ))} +
+
+

{model.review}

+
+ ))} +
+
+ ) +} \ No newline at end of file diff --git a/app/ai/components/ToolReviews.tsx b/app/ai/components/ToolReviews.tsx new file mode 100644 index 0000000..2dff2f4 --- /dev/null +++ b/app/ai/components/ToolReviews.tsx @@ -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 ( +
+

+ + Tool Reviews +

+
+ {reviews.map((review, index) => ( +
+
+

{review.tool}

+
+ {[...Array(5)].map((_, i) => ( + + ))} +
+
+
+
+

Pros:

+
    + {review.pros.map((pro, i) => ( +
  • • {pro}
  • + ))} +
+
+
+

Cons:

+
    + {review.cons.map((con, i) => ( +
  • • {con}
  • + ))} +
+
+
+

{review.verdict}

+
+ ))} +
+
+ ) +} \ No newline at end of file diff --git a/app/ai/components/TopPick.tsx b/app/ai/components/TopPick.tsx new file mode 100644 index 0000000..0ec8dd2 --- /dev/null +++ b/app/ai/components/TopPick.tsx @@ -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 ( +
+

+ + Top Pick of 2025 +

+
+
+
+ +
+

Claude

+

by Anthropic

+
+ + View My Usage + +
+
+
+
+

+ 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. +

+
+
+ Claude Code + Best Tool Calling + High Value in Max Plan + Quite Fast Interface +
+
+ + Top Overall Pick + +
+
+
+
+
+
+ ) +} \ No newline at end of file diff --git a/app/ai/data.tsx b/app/ai/data.tsx new file mode 100644 index 0000000..1e121be --- /dev/null +++ b/app/ai/data.tsx @@ -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: v0, + description: "Generating boilerplate UIs", + status: "occasional", + link: "https://v0.dev/" + }, + { + name: "Qwen", + 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" + }, +] \ No newline at end of file diff --git a/app/ai/page.tsx b/app/ai/page.tsx new file mode 100644 index 0000000..077b779 --- /dev/null +++ b/app/ai/page.tsx @@ -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 ( +
+
+
+
+
+ +
+

AI

+

My journey with using LLMs

+
+ + + +
+ +
+ +
+ + +
+
+
+
+ ) +} \ No newline at end of file diff --git a/app/ai/types.ts b/app/ai/types.ts new file mode 100644 index 0000000..034db85 --- /dev/null +++ b/app/ai/types.ts @@ -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; +} \ No newline at end of file diff --git a/components/Header.tsx b/components/Header.tsx index 68cc54e..11eb756 100644 --- a/components/Header.tsx +++ b/components/Header.tsx @@ -12,7 +12,8 @@ import { X, Menu, Globe, - ChevronDown + ChevronDown, + Brain } from 'lucide-react' import { useTranslation } from 'react-i18next' @@ -145,6 +146,7 @@ export default function Header() {