phase 16: React dashboard with full platform control and analytics
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
import { useState } from 'react';
|
||||
import { usePipelineHealth } from '../api/hooks';
|
||||
import { LoadingSpinner, DateRangeSelector, Card } from '../components/ui';
|
||||
|
||||
export function OpsPipelinePage() {
|
||||
const [hours, setHours] = useState(24);
|
||||
const { data, isLoading } = usePipelineHealth(hours);
|
||||
|
||||
if (isLoading) return <LoadingSpinner />;
|
||||
|
||||
const stages = (data?.document_stages as Array<{ status: string; doc_count: number }>) ?? [];
|
||||
const parsing = (data?.parsing ?? {}) as Record<string, unknown>;
|
||||
const extraction = (data?.extraction ?? {}) as Record<string, unknown>;
|
||||
const aggregation = (data?.aggregation ?? {}) 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">Pipeline Health</h1>
|
||||
<DateRangeSelector value={hours} onChange={setHours} />
|
||||
</div>
|
||||
|
||||
{/* Document Stage Counts */}
|
||||
<Card>
|
||||
<h2 className="mb-3 text-sm font-medium text-gray-400">Document Stages</h2>
|
||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
|
||||
{stages.map((s) => (
|
||||
<div key={s.status} className="rounded-lg border border-surface-700 bg-surface-950 p-3 text-center">
|
||||
<div className="text-2xl font-bold text-gray-100">{s.doc_count}</div>
|
||||
<div className="text-xs capitalize text-gray-500">{s.status}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Parsing Quality */}
|
||||
<Card>
|
||||
<h2 className="mb-3 text-sm font-medium text-gray-400">Parsing Quality</h2>
|
||||
<dl className="grid grid-cols-2 gap-3 text-sm sm:grid-cols-5">
|
||||
<Stat label="Total Parsed" value={parsing.total_parsed} />
|
||||
<Stat label="High Confidence" value={parsing.high_confidence} color="text-green-400" />
|
||||
<Stat label="Medium" value={parsing.medium_confidence} color="text-yellow-400" />
|
||||
<Stat label="Low" value={parsing.low_confidence} color="text-red-400" />
|
||||
<Stat label="Avg Quality" value={parsing.avg_quality_score} />
|
||||
</dl>
|
||||
</Card>
|
||||
|
||||
{/* Extraction Stats */}
|
||||
<Card>
|
||||
<h2 className="mb-3 text-sm font-medium text-gray-400">Extraction Validation</h2>
|
||||
<dl className="grid grid-cols-2 gap-3 text-sm sm:grid-cols-5">
|
||||
<Stat label="Total" value={extraction.total_extractions} />
|
||||
<Stat label="Valid" value={extraction.valid} color="text-green-400" />
|
||||
<Stat label="Failed" value={extraction.failed} color="text-red-400" />
|
||||
<Stat label="Avg Confidence" value={extraction.avg_confidence} />
|
||||
<Stat label="Avg Retries" value={extraction.avg_retries} />
|
||||
</dl>
|
||||
</Card>
|
||||
|
||||
{/* Aggregation */}
|
||||
<Card>
|
||||
<h2 className="mb-3 text-sm font-medium text-gray-400">Trend Generation</h2>
|
||||
<dl className="grid grid-cols-2 gap-3 text-sm sm:grid-cols-4">
|
||||
<Stat label="Trends Generated" value={aggregation.trends_generated} />
|
||||
<Stat label="Symbols Covered" value={aggregation.symbols_covered} />
|
||||
<Stat label="Avg Confidence" value={aggregation.avg_trend_confidence} />
|
||||
<Stat label="Avg Contradiction" value={aggregation.avg_contradiction} />
|
||||
</dl>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Stat({ label, value, color = 'text-gray-100' }: { label: string; value: unknown; color?: string }) {
|
||||
return (
|
||||
<div className="rounded-lg border border-surface-700 bg-surface-950 p-3 text-center">
|
||||
<div className={`text-xl font-bold ${color}`}>{value != null ? String(value) : '—'}</div>
|
||||
<div className="text-xs text-gray-500">{label}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user