phase 16: React dashboard with full platform control and analytics
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
import { useState } from 'react';
|
||||
import { useModelPerformance, useModelFailures } from '../api/hooks';
|
||||
import { LoadingSpinner, DateRangeSelector, StatusBadge, Card } from '../components/ui';
|
||||
|
||||
export function OpsModelPage() {
|
||||
const [hours, setHours] = useState(24);
|
||||
const { data: perf, isLoading } = useModelPerformance(hours);
|
||||
const { data: failures } = useModelFailures(hours);
|
||||
|
||||
if (isLoading) return <LoadingSpinner />;
|
||||
|
||||
const p = (perf ?? {}) as Record<string, unknown>;
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-xl font-semibold text-gray-100">Model Performance</h1>
|
||||
<DateRangeSelector value={hours} onChange={setHours} />
|
||||
</div>
|
||||
|
||||
{/* Key metrics */}
|
||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-5">
|
||||
<StatCard label="Total Extractions" value={p.total_extractions} />
|
||||
<StatCard label="Success Rate" value={p.success_rate != null ? `${((p.success_rate as number) * 100).toFixed(1)}%` : '—'} color="text-green-400" />
|
||||
<StatCard label="Avg Latency" value={p.avg_duration_ms != null ? `${Math.round(p.avg_duration_ms as number)}ms` : '—'} />
|
||||
<StatCard label="Retry Rate" value={p.retry_rate != null ? `${((p.retry_rate as number) * 100).toFixed(1)}%` : '—'} color="text-yellow-400" />
|
||||
<StatCard label="Avg Confidence" value={p.avg_confidence != null ? ((p.avg_confidence as number) * 100).toFixed(0) + '%' : '—'} />
|
||||
</div>
|
||||
|
||||
{/* Recent Failures */}
|
||||
<Card>
|
||||
<h2 className="mb-3 text-sm font-medium text-gray-400">
|
||||
Recent Failures ({(failures as unknown[])?.length ?? 0})
|
||||
</h2>
|
||||
{!(failures as unknown[])?.length ? (
|
||||
<p className="text-sm text-gray-500">No recent failures</p>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{(failures as Array<Record<string, unknown>>).map((f, i) => (
|
||||
<div key={i} className="rounded border border-surface-700 bg-surface-950 p-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-mono text-sm text-brand-300">{f.ticker as string}</span>
|
||||
<StatusBadge status="failed" />
|
||||
<span className="text-xs text-gray-500">{f.model_name as string}</span>
|
||||
</div>
|
||||
<span className="text-xs text-gray-500">{f.recorded_at ? new Date(f.recorded_at as string).toLocaleString() : ''}</span>
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-gray-400">
|
||||
{f.document_title as string} ({f.document_type as string})
|
||||
</div>
|
||||
{f.validation_errors && (
|
||||
<div className="mt-1 text-xs text-red-400">
|
||||
{JSON.stringify(f.validation_errors)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function StatCard({ label, value, color = 'text-gray-100' }: { label: string; value: unknown; color?: string }) {
|
||||
return (
|
||||
<Card className="text-center">
|
||||
<div className={`text-xl font-bold ${color}`}>{value != null ? String(value) : '—'}</div>
|
||||
<div className="text-xs text-gray-500">{label}</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user