Feat: Live-Charts auf Diagnose + Token-Durchsatz-Performance-Karte (Recharts)
Gemeinsame LiveAreaChart-Komponente + useSystemHistory-Hook (EINE Quelle der Wahrheit fuer den Live-Verlauf), genutzt von Zentrale & Diagnose. - Diagnose: die 4 statischen Balken (CPU/RAM/GPU/Disk) sind jetzt farbcodierte Einzel-Live-Charts (Spline, Gradient, Hover-Tooltip, dyn. Y-Achse). - Neue TokenPerformanceCard (Zentrale): Live-Durchsatz tok/s, aus den kumulativen Token-Zaehlern als Rate abgeleitet (Prompt/Prefill vs. Antwort/Generierung), KPI- Headline (tok/s, Gesamt-Tokens, gespart EUR), dunkler Performance-Stil. - SystemStatusCard auf die geteilte Komponente/Hook umgestellt (schlanker). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
import { Area, AreaChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts"
|
||||
|
||||
export type ChartSeries = { key: string; label: string; color: string }
|
||||
|
||||
const GRID = "rgba(130,130,150,0.14)"
|
||||
const AXIS = "rgba(130,130,150,0.85)"
|
||||
|
||||
function niceCeil(v: number): number {
|
||||
if (v <= 0) return 10
|
||||
const mag = Math.pow(10, Math.floor(Math.log10(v)))
|
||||
const n = v / mag
|
||||
const step = n <= 1 ? 1 : n <= 2 ? 2 : n <= 5 ? 5 : 10
|
||||
return step * mag
|
||||
}
|
||||
|
||||
function fmt(v: number, unit: string): string {
|
||||
const n = unit === "%" ? Math.round(v) : v >= 1000 ? `${(v / 1000).toFixed(1)}k` : Math.round(v).toString()
|
||||
return `${n}${unit}`
|
||||
}
|
||||
|
||||
function ChartTooltip({ active, payload, unit }: any) {
|
||||
if (!active || !payload?.length) return null
|
||||
return (
|
||||
<div className="rounded-lg border border-border/70 bg-popover/95 px-3 py-2 shadow-xl backdrop-blur">
|
||||
<div className="space-y-1">
|
||||
{payload.map((p: any) => (
|
||||
<div key={p.dataKey} className="flex items-center gap-2 text-[11px] font-mono">
|
||||
<span className="h-2 w-2 rounded-full" style={{ background: p.color }} />
|
||||
<span className="uppercase tracking-wider text-muted-foreground">{p.name}</span>
|
||||
<span className="ml-auto pl-3 font-bold tabular-nums text-foreground">{fmt(p.value, unit)}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/** Wiederverwendbarer Live-Verlaufsgraph (Recharts Area, glatte Splines, Gradient-Fill,
|
||||
* Hover-Tooltip). yMode='percent' → 0..100 in 25er-Schritten; 'auto' → dynamisch (nice). */
|
||||
export function LiveAreaChart({
|
||||
data, series, unit = "%", yMode = "percent", height = 176,
|
||||
}: {
|
||||
data: any[]
|
||||
series: ChartSeries[]
|
||||
unit?: string
|
||||
yMode?: "percent" | "auto"
|
||||
height?: number
|
||||
}) {
|
||||
const peak = data.reduce(
|
||||
(m, row) => series.reduce((mm, s) => Math.max(mm, Number(row[s.key]) || 0), m), 0
|
||||
)
|
||||
const yMax = yMode === "percent"
|
||||
? Math.min(100, Math.max(25, Math.ceil((peak * 1.2) / 25) * 25))
|
||||
: Math.max(niceCeil(peak * 1.15), 10)
|
||||
|
||||
return (
|
||||
<div style={{ height }} className="w-full">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<AreaChart data={data} margin={{ top: 8, right: 6, bottom: 0, left: -12 }}>
|
||||
<defs>
|
||||
{series.map((s) => (
|
||||
<linearGradient key={s.key} id={`grad-${s.key}`} x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor={s.color} stopOpacity={0.22} />
|
||||
<stop offset="100%" stopColor={s.color} stopOpacity={0} />
|
||||
</linearGradient>
|
||||
))}
|
||||
</defs>
|
||||
<CartesianGrid vertical={false} stroke={GRID} />
|
||||
<XAxis dataKey="t" hide />
|
||||
<YAxis
|
||||
domain={[0, yMax]} ticks={[0, yMax / 2, yMax]} tickFormatter={(v) => fmt(v, unit)}
|
||||
width={42} axisLine={false} tickLine={false} tick={{ fontSize: 10, fill: AXIS }}
|
||||
/>
|
||||
<Tooltip content={<ChartTooltip unit={unit} />} cursor={{ stroke: AXIS, strokeOpacity: 0.4, strokeDasharray: "3 3" }} />
|
||||
{series.map((s) => (
|
||||
<Area
|
||||
key={s.key} type="monotone" dataKey={s.key} name={s.label}
|
||||
stroke={s.color} strokeWidth={2} fill={`url(#grad-${s.key})`}
|
||||
dot={false} activeDot={{ r: 3, strokeWidth: 0 }}
|
||||
isAnimationActive={false} connectNulls
|
||||
/>
|
||||
))}
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user