feat/fix: ai page improvements, bump, update ccusage
add more content on ai, add heatmap, fix header glow, add price to ai tools, fix NowPlaying style issue, new ccombine utility, better claude code usage page w/ model labels+better typing, bump, update ccusage (restore old dates)
This commit is contained in:
parent
57dd627ca3
commit
77a6266c71
20 changed files with 1380 additions and 444 deletions
172
app/ai/claude/components/Activity.tsx
Normal file
172
app/ai/claude/components/Activity.tsx
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
"use client"
|
||||
|
||||
import { useMemo, useState } from 'react'
|
||||
import {
|
||||
AreaChart,
|
||||
Area,
|
||||
CartesianGrid,
|
||||
XAxis,
|
||||
YAxis,
|
||||
Tooltip,
|
||||
ResponsiveContainer,
|
||||
} from 'recharts'
|
||||
import { DailyData } from './types'
|
||||
import {
|
||||
buildDailyTrendData,
|
||||
formatCurrency,
|
||||
formatTokens,
|
||||
getHeatmapColor,
|
||||
prepareHeatmapData,
|
||||
} from './utils'
|
||||
|
||||
export default function Activity({ daily }: { daily: DailyData[] }) {
|
||||
const [viewMode, setViewMode] = useState<'heatmap' | 'chart'>('heatmap')
|
||||
const [selectedMetric, setSelectedMetric] = useState<'cost' | 'tokens'>('cost')
|
||||
|
||||
const dailyTrendData = useMemo(() => buildDailyTrendData(daily), [daily])
|
||||
const heatmapWeeks = useMemo(() => prepareHeatmapData(daily), [daily])
|
||||
const maxCost = useMemo(
|
||||
() => (daily.length ? Math.max(...daily.map(d => d.totalCost)) : 0),
|
||||
[daily]
|
||||
)
|
||||
|
||||
return (
|
||||
<section className="p-8 border-2 border-gray-700 rounded-lg hover:border-gray-600 transition-colors duration-300 relative md:col-span-2 lg:col-span-1">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h2 className="text-2xl font-semibold text-gray-200">Activity</h2>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm text-gray-400">{viewMode === 'heatmap' ? 'Heatmap' : 'Chart'}</span>
|
||||
<button
|
||||
onClick={() => setViewMode(viewMode === 'heatmap' ? 'chart' : 'heatmap')}
|
||||
className="relative inline-flex h-6 w-11 items-center rounded-full bg-gray-700 transition-colors focus:outline-none focus:ring-2 focus:ring-[#c15f3c] focus:ring-offset-2 focus:ring-offset-gray-900"
|
||||
>
|
||||
<span className="sr-only">Toggle view mode</span>
|
||||
<span
|
||||
className={`${viewMode === 'chart' ? 'translate-x-6' : 'translate-x-1'} inline-block h-4 w-4 transform rounded-full bg-[#c15f3c] transition-transform`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{viewMode === 'heatmap' ? (
|
||||
<div className="overflow-x-auto pb-6">
|
||||
<div className="min-w-[900px]">
|
||||
<div className="flex gap-1">
|
||||
<div className="flex flex-col gap-1 text-xs text-gray-400 w-10 pr-2">
|
||||
<div className="h-4"></div>
|
||||
{['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].map((day) => (
|
||||
<div key={day} className="h-4 flex items-center justify-end text-[10px]">
|
||||
{day}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="relative">
|
||||
<div className="h-4 mb-1 text-xs text-gray-400">
|
||||
{(() => {
|
||||
const monthLabels: { month: string; position: number }[] = []
|
||||
let lastMonth = -1
|
||||
heatmapWeeks.forEach((week, weekIndex) => {
|
||||
const firstDay = week.find(d => d !== null)
|
||||
if (firstDay) {
|
||||
const date = new Date(firstDay.date + 'T00:00:00Z')
|
||||
const month = date.getUTCMonth()
|
||||
if (month !== lastMonth) {
|
||||
monthLabels.push({
|
||||
month: date.toLocaleDateString('en-US', { month: 'short', timeZone: 'UTC' }),
|
||||
position: weekIndex * 20
|
||||
})
|
||||
lastMonth = month
|
||||
}
|
||||
}
|
||||
})
|
||||
return (
|
||||
<div className="flex relative">
|
||||
{monthLabels.map((label, idx) => (
|
||||
<div key={idx} style={{ position: 'absolute', left: label.position }} className="w-10">
|
||||
{label.month}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
})()}
|
||||
</div>
|
||||
<div className="flex gap-1">
|
||||
{heatmapWeeks.map((week, weekIndex) => (
|
||||
<div key={weekIndex} className="flex flex-col gap-1">
|
||||
{week.map((day, dayIndex) => (
|
||||
<div key={dayIndex} className="relative group">
|
||||
<div
|
||||
className="w-4 h-4 rounded-sm"
|
||||
style={{ backgroundColor: getHeatmapColor(maxCost, day?.value || 0) }}
|
||||
/>
|
||||
{day && (
|
||||
<div className="absolute z-10 invisible group-hover:visible -top-2 left-6">
|
||||
<div className="bg-gray-900 border border-gray-700 rounded-lg p-2 shadow-lg whitespace-nowrap">
|
||||
<p className="text-gray-300 text-xs font-medium mb-1">{day.formattedDate}</p>
|
||||
<p className="text-[#c15f3c] font-bold text-sm">Cost: ${day.cost.toFixed(2)}</p>
|
||||
<p className="text-gray-400 text-xs">Tokens: {(day.tokens / 1000000).toFixed(2)}M</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 mt-4 text-xs text-gray-400">
|
||||
<span>Less</span>
|
||||
<div className="flex gap-1">
|
||||
<div className="w-3 h-3 rounded-sm" style={{ backgroundColor: '#1f2937' }}></div>
|
||||
<div className="w-3 h-3 rounded-sm" style={{ backgroundColor: '#4a3328' }}></div>
|
||||
<div className="w-3 h-3 rounded-sm" style={{ backgroundColor: '#6b4530' }}></div>
|
||||
<div className="w-3 h-3 rounded-sm" style={{ backgroundColor: '#8d5738' }}></div>
|
||||
<div className="w-3 h-3 rounded-sm" style={{ backgroundColor: '#c15f3c' }}></div>
|
||||
</div>
|
||||
<span>More</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<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={400}>
|
||||
<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>
|
||||
)
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue