44 AreaChart , Area , BarChart , Bar ,
55 ResponsiveContainer , XAxis , YAxis , Tooltip , CartesianGrid ,
66} from 'recharts'
7- import { useEvents } from '../api/hooks'
7+ import { useEvents , useEventStats } from '../api/hooks'
88import {
99 Loader2 , Search , ChevronDown , ChevronLeft , ChevronRight ,
1010 Download , Radio , GitCompareArrows , ExternalLink ,
@@ -439,6 +439,7 @@ function ensureStyles() {
439439/* ---- Main Component ---- */
440440export function Events ( ) {
441441 const { data : events , isLoading } = useEvents ( )
442+ const { data : serverStats } = useEventStats ( )
442443 const [ search , setSearch ] = useState ( '' )
443444 const [ sourceFilter , setSourceFilter ] = useState < string > ( 'all' )
444445 const [ modelFilter , setModelFilter ] = useState < string > ( 'all' )
@@ -526,7 +527,17 @@ export function Events() {
526527 return list
527528 } , [ allEvents , sourceFilter , modelFilter , search , sortField , sortDir ] )
528529
529- const stats = useMemo ( ( ) => computeStats ( allEvents ) , [ allEvents ] )
530+ const clientStats = useMemo ( ( ) => computeStats ( allEvents ) , [ allEvents ] )
531+ const stats = serverStats != null
532+ ? {
533+ totalReviews : serverStats . total_reviews ,
534+ avgDuration : serverStats . avg_duration_ms ,
535+ totalTokens : serverStats . total_tokens ,
536+ avgScore : serverStats . avg_score ?? 0 ,
537+ failedCount : serverStats . failed_count ,
538+ timeline : clientStats . timeline ,
539+ }
540+ : clientStats
530541 const totalPages = Math . max ( 1 , Math . ceil ( filtered . length / PAGE_SIZE ) )
531542 const paginated = filtered . slice ( ( page - 1 ) * PAGE_SIZE , page * PAGE_SIZE )
532543
@@ -573,15 +584,15 @@ export function Events() {
573584 </ button >
574585 </ div >
575586
576- { /* Stat cards */ }
587+ { /* Stat cards (server stats when available) */ }
577588 < div className = "grid grid-cols-2 md:grid-cols-6 gap-3 mb-6" >
578589 { [
579590 { label : 'REVIEWS' , value : String ( stats . totalReviews ) , sub : stats . failedCount > 0 ? `${ stats . failedCount } failed` : undefined , subColor : 'text-sev-error' } ,
580591 { label : 'AVG DURATION' , value : formatDuration ( stats . avgDuration ) } ,
581592 { label : 'TOTAL TOKENS' , value : fmtTokens ( stats . totalTokens ) } ,
582593 { label : 'AVG SCORE' , value : stats . avgScore . toFixed ( 1 ) , valueColor : stats . avgScore >= 7 ? 'text-sev-suggestion' : stats . avgScore >= 4 ? 'text-sev-warning' : 'text-sev-error' } ,
583594 { label : 'TOTAL FILES' , value : String ( allEvents . reduce ( ( s , e ) => s + e . diff_files_reviewed , 0 ) ) } ,
584- { label : 'TOTAL COST' , value : formatCost ( totalCost ( allEvents ) ) } ,
595+ { label : 'TOTAL COST' , value : formatCost ( serverStats != null ? serverStats . total_cost_estimate : totalCost ( allEvents ) ) } ,
585596 ] . map ( card => (
586597 < div key = { card . label } className = "bg-surface-1 border border-border rounded-lg p-3" >
587598 < div className = "text-[10px] font-semibold text-text-muted tracking-[0.08em] font-code" > { card . label } </ div >
@@ -591,6 +602,61 @@ export function Events() {
591602 ) ) }
592603 </ div >
593604
605+ { /* Server latency percentiles + by-model + daily (when available) */ }
606+ { serverStats && ( serverStats . p50_latency_ms > 0 || serverStats . p95_latency_ms > 0 || serverStats . p99_latency_ms > 0 ) && (
607+ < div className = "grid grid-cols-3 gap-3 mb-6" >
608+ < div className = "bg-surface-1 border border-border rounded-lg p-3" >
609+ < div className = "text-[10px] font-semibold text-text-muted tracking-[0.08em] font-code" > P50 LATENCY</ div >
610+ < div className = "text-lg font-bold font-code mt-1 text-text-primary" > { formatDuration ( serverStats . p50_latency_ms ) } </ div >
611+ </ div >
612+ < div className = "bg-surface-1 border border-border rounded-lg p-3" >
613+ < div className = "text-[10px] font-semibold text-text-muted tracking-[0.08em] font-code" > P95 LATENCY</ div >
614+ < div className = "text-lg font-bold font-code mt-1 text-text-primary" > { formatDuration ( serverStats . p95_latency_ms ) } </ div >
615+ </ div >
616+ < div className = "bg-surface-1 border border-border rounded-lg p-3" >
617+ < div className = "text-[10px] font-semibold text-text-muted tracking-[0.08em] font-code" > P99 LATENCY</ div >
618+ < div className = "text-lg font-bold font-code mt-1 text-text-primary" > { formatDuration ( serverStats . p99_latency_ms ) } </ div >
619+ </ div >
620+ </ div >
621+ ) }
622+ { serverStats && ( serverStats . by_model . length > 0 || serverStats . daily_counts . length > 0 ) && (
623+ < div className = "grid grid-cols-1 md:grid-cols-2 gap-3 mb-6" >
624+ { serverStats . by_model . length > 0 && (
625+ < div className = "bg-surface-1 border border-border rounded-lg p-4" >
626+ < div className = "text-[10px] font-semibold text-text-muted tracking-[0.08em] font-code mb-3" > REVIEWS BY MODEL</ div >
627+ < div className = "space-y-1.5" >
628+ { serverStats . by_model . map ( m => (
629+ < div key = { m . model } className = "flex items-center justify-between text-[11px]" >
630+ < span className = "font-code text-text-primary truncate max-w-40" title = { m . model } > { m . model } </ span >
631+ < span className = "font-code text-text-secondary tabular-nums" > { m . count } reviews · { formatDuration ( m . avg_duration_ms ) } avg</ span >
632+ </ div >
633+ ) ) }
634+ </ div >
635+ </ div >
636+ ) }
637+ { serverStats . daily_counts . length > 0 && (
638+ < div className = "bg-surface-1 border border-border rounded-lg p-4" >
639+ < div className = "text-[10px] font-semibold text-text-muted tracking-[0.08em] font-code mb-3" > DAILY ACTIVITY</ div >
640+ < div className = "h-28" >
641+ < ResponsiveContainer width = "100%" height = "99%" minWidth = { 50 } minHeight = { 50 } >
642+ < BarChart
643+ data = { serverStats . daily_counts . slice ( - 14 ) . map ( d => ( { date : d . date . slice ( 0 , 10 ) , completed : d . completed , failed : d . failed } ) ) }
644+ margin = { { top : 4 , right : 4 , left : 0 , bottom : 0 } }
645+ >
646+ < CartesianGrid { ...gridProps } />
647+ < XAxis dataKey = "date" tick = { { ...axisTick , fontSize : 9 } } axisLine = { false } tickLine = { false } />
648+ < YAxis tick = { axisTick } axisLine = { false } tickLine = { false } />
649+ < Tooltip { ...tooltipStyle } />
650+ < Bar dataKey = "completed" name = "Completed" fill = { CHART_THEME . accent } radius = { [ 2 , 2 , 0 , 0 ] } stackId = "a" />
651+ < Bar dataKey = "failed" name = "Failed" fill = "#ef4444" radius = { [ 2 , 2 , 0 , 0 ] } stackId = "a" />
652+ </ BarChart >
653+ </ ResponsiveContainer >
654+ </ div >
655+ </ div >
656+ ) }
657+ </ div >
658+ ) }
659+
594660 { /* Charts */ }
595661 { stats . timeline . length > 1 && (
596662 < div className = "grid grid-cols-1 md:grid-cols-2 gap-3 mb-6" >
0 commit comments