import { useEffect, useState } from 'react'; import { RefreshCw, AlertCircle, CheckCircle2 } from 'lucide-react'; import Card from '../ui/Card'; import Button from '../ui/Button'; import Tag from '../ui/Tag'; import * as db from '../../lib/db'; // Public Anthropic pricing per 1M tokens. Update manually when prices change. const PRICES = { 'claude-haiku-4-5-20251001': { input: 1.0, output: 5.0, cache_read: 0.10, cache_write: 1.25 }, 'claude-haiku-4-5': { input: 1.0, output: 5.0, cache_read: 0.10, cache_write: 1.25 }, 'claude-sonnet-4-6': { input: 3.0, output: 15.0, cache_read: 0.30, cache_write: 3.75 }, 'claude-opus-4-7': { input: 15.0, output: 75.0, cache_read: 1.50, cache_write: 18.75 }, }; function pricesFor(model) { if (!model) return null; if (PRICES[model]) return PRICES[model]; if (model.includes('haiku')) return PRICES['claude-haiku-4-5']; if (model.includes('sonnet')) return PRICES['claude-sonnet-4-6']; if (model.includes('opus')) return PRICES['claude-opus-4-7']; return null; } function costUsd(row) { const p = pricesFor(row.model); if (!p) return null; const inTok = (row.input_tokens || 0) - (row.cache_read_tokens || 0) - (row.cache_create_tokens || 0); const out = row.output_tokens || 0; const cr = row.cache_read_tokens || 0; const cc = row.cache_create_tokens || 0; const usd = (Math.max(inTok, 0) * p.input + out * p.output + cr * p.cache_read + cc * p.cache_write) / 1_000_000; return usd; } function fmtUsd(n) { if (n == null) return '—'; if (n < 0.0001) return '<$0.0001'; return `$${n.toFixed(4)}`; } function fmtMs(n) { if (n == null) return '—'; if (n < 1000) return `${Math.round(n)}ms`; return `${(n / 1000).toFixed(1)}s`; } const Diagnostics = () => { const [rows, setRows] = useState([]); const [loading, setLoading] = useState(false); const load = async () => { setLoading(true); try { const r = await db.getRecentLlmCalls(100); setRows(r); } finally { setLoading(false); } }; useEffect(() => { load(); }, []); const totals = rows.reduce((acc, r) => { acc.input += r.input_tokens || 0; acc.output += r.output_tokens || 0; acc.cacheRead += r.cache_read_tokens || 0; acc.cacheCreate += r.cache_create_tokens || 0; const c = costUsd(r); if (c != null) acc.cost += c; acc.duration += r.duration_ms || 0; if (r.ok) acc.ok++; else acc.fail++; return acc; }, { input: 0, output: 0, cacheRead: 0, cacheCreate: 0, cost: 0, duration: 0, ok: 0, fail: 0 }); const cacheHitRate = totals.input > 0 ? Math.round((totals.cacheRead / totals.input) * 100) : 0; return (

Laatste 100 LLM-aanroepen. Kosten worden lokaal berekend met publieke Anthropic-prijzen (handmatig verversen).

Calls

{rows.length}

{totals.ok} ok · {totals.fail} fail

Tokens (in/out)

{totals.input.toLocaleString()} / {totals.output.toLocaleString()}

cache read: {totals.cacheRead.toLocaleString()} ({cacheHitRate}%)

Geschatte kosten

{fmtUsd(totals.cost)}

over deze 100 aanroepen

Gem. duur

{rows.length ? fmtMs(totals.duration / rows.length) : '—'}

{rows.length === 0 ? ( ) : rows.map(r => ( ))}
Tijd Task Tier Model In Out Cache $ Duur Status
Nog geen aanroepen geregistreerd.
{r.created ? new Date(r.created).toLocaleString() : '—'} {r.task || '—'} {r.tier || '—'} {r.model || '—'} {(r.input_tokens || 0).toLocaleString()} {(r.output_tokens || 0).toLocaleString()} {(r.cache_read_tokens || 0).toLocaleString()} {fmtUsd(costUsd(r))} {fmtMs(r.duration_ms)} {r.ok ? ok : {(r.error_msg || r.stop_reason || 'fail').slice(0, 24)}}
); }; export default Diagnostics;