/ src / components / usage / usage-list.tsx
usage-list.tsx
  1  'use client'
  2  
  3  import { useEffect, useState } from 'react'
  4  import { api } from '@/lib/app/api-client'
  5  
  6  interface UsageData {
  7    totalTokens: number
  8    totalCost: number
  9    byProvider: Record<string, { cost: number; tokens: number; requests: number }>
 10    bySession: Record<string, { cost: number; tokens: number; requests: number }>
 11    raw: unknown[]
 12  }
 13  
 14  export function UsageList() {
 15    const [data, setData] = useState<UsageData | null>(null)
 16    const [loading, setLoading] = useState(true)
 17  
 18    useEffect(() => {
 19      api<UsageData>('GET', '/usage')
 20        .then(setData)
 21        .catch(() => {})
 22        .finally(() => setLoading(false))
 23    }, [])
 24  
 25    if (loading) {
 26      return (
 27        <div className="flex-1 flex items-center justify-center text-text-3 text-[13px]">
 28          Loading usage...
 29        </div>
 30      )
 31    }
 32  
 33    if (!data) {
 34      return (
 35        <div className="flex-1 flex items-center justify-center text-text-3 text-[13px]">
 36          Failed to load usage data
 37        </div>
 38      )
 39    }
 40  
 41    const providers = Object.entries(data.byProvider || {}).sort(
 42      ([, a], [, b]) => b.cost - a.cost
 43    )
 44  
 45    const formatCost = (cost: number) =>
 46      cost < 0.01 ? `$${cost.toFixed(4)}` : `$${cost.toFixed(2)}`
 47  
 48    const formatTokens = (tokens: number) => {
 49      if (tokens >= 1_000_000) return `${(tokens / 1_000_000).toFixed(1)}M`
 50      if (tokens >= 1_000) return `${(tokens / 1_000).toFixed(1)}K`
 51      return String(tokens)
 52    }
 53  
 54    return (
 55      <div className="flex-1 overflow-y-auto px-5 pb-8">
 56        {/* Summary */}
 57        <div className="grid grid-cols-2 md:grid-cols-4 gap-3 mb-4 mt-1">
 58          <div className="p-3 rounded-[12px] bg-white/[0.03] border border-white/[0.06]">
 59            <div className="text-[10px] font-600 text-text-3 uppercase tracking-wider mb-1">Total Cost</div>
 60            <div className="text-[18px] font-700 text-text tracking-tight">{formatCost(data.totalCost)}</div>
 61          </div>
 62          <div className="p-3 rounded-[12px] bg-white/[0.03] border border-white/[0.06]">
 63            <div className="text-[10px] font-600 text-text-3 uppercase tracking-wider mb-1">Total Tokens</div>
 64            <div className="text-[18px] font-700 text-text tracking-tight">{formatTokens(data.totalTokens)}</div>
 65          </div>
 66          <div className="p-3 rounded-[12px] bg-white/[0.03] border border-white/[0.06]">
 67            <div className="text-[10px] font-600 text-text-3 uppercase tracking-wider mb-1">Total Requests</div>
 68            <div className="text-[18px] font-700 text-text tracking-tight">{providers.reduce((sum, [, s]) => sum + s.requests, 0)}</div>
 69          </div>
 70          <div className="p-3 rounded-[12px] bg-white/[0.03] border border-white/[0.06]">
 71            <div className="text-[10px] font-600 text-text-3 uppercase tracking-wider mb-1">Providers</div>
 72            <div className="text-[18px] font-700 text-text tracking-tight">{providers.length}</div>
 73          </div>
 74        </div>
 75  
 76        {/* Provider breakdown */}
 77        <div className="mb-2">
 78          <h3 className="text-[11px] font-600 text-text-3 uppercase tracking-wider mb-2">By Provider</h3>
 79          {providers.length === 0 ? (
 80            <div className="text-center py-6 text-[12px] text-text-3/60">No usage data yet</div>
 81          ) : (
 82            <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
 83              {providers.map(([provider, stats]) => {
 84                const pct = data.totalCost > 0 ? (stats.cost / data.totalCost) * 100 : 0
 85                return (
 86                  <div
 87                    key={provider}
 88                    className="p-3 rounded-[10px] bg-surface border border-white/[0.06] hover:bg-surface-2 transition-colors"
 89                  >
 90                    <div className="flex items-center justify-between mb-1.5">
 91                      <span className="text-[13px] font-600 text-text capitalize">{provider}</span>
 92                      <span className="text-[13px] font-700 text-text tabular-nums">{formatCost(stats.cost)}</span>
 93                    </div>
 94                    <div className="flex items-center gap-3 text-[11px] text-text-3">
 95                      <span>{formatTokens(stats.tokens)} tokens</span>
 96                      <span>{stats.requests} requests</span>
 97                      <span className="ml-auto">{pct.toFixed(1)}%</span>
 98                    </div>
 99                    <div className="mt-2 h-1 rounded-full bg-white/[0.04] overflow-hidden">
100                      <div
101                        className="h-full rounded-full bg-accent-bright/60"
102                        style={{ width: `${Math.max(pct, 1)}%` }}
103                      />
104                    </div>
105                  </div>
106                )
107              })}
108            </div>
109          )}
110        </div>
111      </div>
112    )
113  }