refactor: remove Diagnostics component and related LLM call telemetry
All checks were successful
On Push to Main / test (push) Successful in 31s
On Push to Main / publish (push) Successful in 58s
On Push to Main / deploy-dev (push) Successful in 1m30s

This commit is contained in:
RaymondVerhoef
2026-05-22 20:00:47 +02:00
parent 7b6ae265db
commit 881148357e
3 changed files with 1 additions and 184 deletions

View File

@@ -1,162 +0,0 @@
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 (
<div>
<div className="flex items-center justify-between mb-4">
<p className="text-fg-muted text-sm">
Laatste 100 LLM-aanroepen. Kosten worden lokaal berekend met publieke Anthropic-prijzen (handmatig verversen).
</p>
<Button onClick={load} disabled={loading}>
<RefreshCw size={16} className={`mr-2 ${loading ? 'animate-spin' : ''}`} /> Vernieuwen
</Button>
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 mb-6">
<Card className="p-4 border border-bg-warm">
<p className="text-xs text-fg-muted uppercase">Calls</p>
<p className="text-2xl font-bold mt-1">{rows.length}</p>
<p className="text-xs text-fg-muted mt-1">{totals.ok} ok · {totals.fail} fail</p>
</Card>
<Card className="p-4 border border-bg-warm">
<p className="text-xs text-fg-muted uppercase">Tokens (in/out)</p>
<p className="text-2xl font-bold mt-1">{totals.input.toLocaleString()} / {totals.output.toLocaleString()}</p>
<p className="text-xs text-fg-muted mt-1">cache read: {totals.cacheRead.toLocaleString()} ({cacheHitRate}%)</p>
</Card>
<Card className="p-4 border border-bg-warm">
<p className="text-xs text-fg-muted uppercase">Geschatte kosten</p>
<p className="text-2xl font-bold mt-1">{fmtUsd(totals.cost)}</p>
<p className="text-xs text-fg-muted mt-1">over deze 100 aanroepen</p>
</Card>
<Card className="p-4 border border-bg-warm">
<p className="text-xs text-fg-muted uppercase">Gem. duur</p>
<p className="text-2xl font-bold mt-1">{rows.length ? fmtMs(totals.duration / rows.length) : '—'}</p>
</Card>
</div>
<Card className="p-0 border border-bg-warm overflow-hidden">
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead className="bg-bg-warm/40 text-fg-muted text-xs uppercase">
<tr>
<th className="text-left p-3">Tijd</th>
<th className="text-left p-3">Task</th>
<th className="text-left p-3">Tier</th>
<th className="text-left p-3">Model</th>
<th className="text-right p-3">In</th>
<th className="text-right p-3">Out</th>
<th className="text-right p-3">Cache</th>
<th className="text-right p-3">$</th>
<th className="text-right p-3">Duur</th>
<th className="text-left p-3">Status</th>
</tr>
</thead>
<tbody className="divide-y divide-bg-warm">
{rows.length === 0 ? (
<tr><td colSpan={10} className="p-8 text-center text-fg-muted">Nog geen aanroepen geregistreerd.</td></tr>
) : rows.map(r => (
<tr key={r.id} className="hover:bg-bg-warm/20">
<td className="p-3 text-xs text-fg-muted whitespace-nowrap">
{r.created ? new Date(r.created).toLocaleString() : '—'}
</td>
<td className="p-3 font-mono text-xs">{r.task || '—'}</td>
<td className="p-3 text-xs">{r.tier || '—'}</td>
<td className="p-3 font-mono text-xs">{r.model || '—'}</td>
<td className="p-3 text-right">{(r.input_tokens || 0).toLocaleString()}</td>
<td className="p-3 text-right">{(r.output_tokens || 0).toLocaleString()}</td>
<td className="p-3 text-right text-fg-muted">{(r.cache_read_tokens || 0).toLocaleString()}</td>
<td className="p-3 text-right">{fmtUsd(costUsd(r))}</td>
<td className="p-3 text-right">{fmtMs(r.duration_ms)}</td>
<td className="p-3">
{r.ok
? <Tag variant="success" className="flex items-center gap-1 w-fit"><CheckCircle2 size={12}/> ok</Tag>
: <Tag variant="dark" className="bg-red-100 text-red-800 flex items-center gap-1 w-fit"><AlertCircle size={12}/> {(r.error_msg || r.stop_reason || 'fail').slice(0, 24)}</Tag>}
</td>
</tr>
))}
</tbody>
</table>
</div>
</Card>
</div>
);
};
export default Diagnostics;

View File

@@ -299,17 +299,6 @@ export async function bulkSetCurriculum(year, weeks) {
);
}
// ── LLM Call Telemetry ───────────────────────────────────────────────────────
export async function getRecentLlmCalls(limit = 100) {
try {
const r = await pb.collection('llm_calls').getList(1, limit, { sort: '-created' });
return r.items;
} catch {
return [];
}
}
// ── Reset for Smoke Testing ──────────────────────────────────────────────────
/**

View File

@@ -1,5 +1,5 @@
import { useState, useEffect } from 'react';
import { Database, FileText, Settings, Users, Network, Clock, CheckCircle2, AlertCircle, Save, Info, Layers, CheckSquare, CalendarDays, Activity, RefreshCw, AlertTriangle } from 'lucide-react';
import { Database, FileText, Settings, Users, Network, Clock, CheckCircle2, AlertCircle, Save, Info, Layers, CheckSquare, CalendarDays, RefreshCw, AlertTriangle } from 'lucide-react';
import Card from '../../components/ui/Card';
import Tag from '../../components/ui/Tag';
import Button from '../../components/ui/Button';
@@ -12,7 +12,6 @@ import ContentManager from '../../components/admin/ContentManager';
import TestManager from '../../components/admin/TestManager';
import TeamManager from '../../components/admin/TeamManager';
import CurriculumManager from '../../components/admin/CurriculumManager';
import Diagnostics from '../../components/admin/Diagnostics';
import { Trash2 } from 'lucide-react';
const TIER_PLACEHOLDERS = {
@@ -93,7 +92,6 @@ const Admin = () => {
{ key: 'curriculum', icon: CalendarDays, label: 'Curriculum' },
{ key: 'graph', icon: Network, label: 'Graph' },
{ key: 'team', icon: Users, label: 'Team' },
{ key: 'diagnostics', icon: Activity, label: 'Diagnostics' },
{ key: 'settings', icon: Settings, label: 'Settings', bottom: true },
];
@@ -204,14 +202,6 @@ const Admin = () => {
</div>
)}
{activeTab === 'diagnostics' && (
<div className="animate-in fade-in duration-300 max-w-6xl mx-auto">
<h1 className="text-3xl text-teal mb-2">Diagnostics</h1>
<p className="text-fg-muted mb-8">LLM-aanroepen, tokenverbruik en geschatte kosten.</p>
<Diagnostics />
</div>
)}
{activeTab === 'settings' && (
<div className="animate-in fade-in duration-300 max-w-2xl">
<h1 className="text-3xl text-teal mb-2">Settings</h1>