agent-ecosystem/src/renderer/components/report/sections/TimelineSection.tsx
Paul Holstein 820bb124d5 feat(report): add threshold-based assessment badges to all report sections
Add interpretive assessment layer to session reports. Every metric section
now shows color-coded severity badges (green/amber/red) computed from
configurable thresholds, replacing raw numbers with instant signal.

Includes centralized reportAssessments utility, model mismatch detection,
switch pattern recognition, and 44 new tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 18:01:43 -05:00

109 lines
4 KiB
TypeScript

import { assessmentColor, assessmentLabel } from '@renderer/utils/reportAssessments';
import { Clock } from 'lucide-react';
import { ReportSection } from '../ReportSection';
import type {
KeyEvent,
ReportIdleAnalysis,
ReportModelSwitches,
} from '@renderer/types/sessionReport';
interface TimelineSectionProps {
idle: ReportIdleAnalysis;
modelSwitches: ReportModelSwitches;
keyEvents: KeyEvent[];
}
export const TimelineSection = ({ idle, modelSwitches, keyEvents }: TimelineSectionProps) => {
const idleColor = assessmentColor(idle.idleAssessment);
return (
<ReportSection title="Timeline & Activity" icon={Clock}>
{/* Idle stats */}
<div className="mb-4">
<div className="mb-2 flex items-center gap-2">
<span className="text-xs font-medium text-text-muted">Idle Analysis</span>
<span
className="rounded px-2 py-0.5 text-xs font-medium"
style={{ backgroundColor: `${idleColor}20`, color: idleColor }}
>
{assessmentLabel(idle.idleAssessment)}
</span>
</div>
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
<div>
<div className="text-xs text-text-muted">Idle Gaps</div>
<div className="text-sm font-medium text-text">{idle.idleGapCount}</div>
</div>
<div>
<div className="text-xs text-text-muted">Total Idle</div>
<div className="text-sm font-medium text-text">{idle.totalIdleHuman}</div>
</div>
<div>
<div className="text-xs text-text-muted">Active Time</div>
<div className="text-sm font-medium text-text">{idle.activeWorkingHuman}</div>
</div>
<div>
<div className="text-xs text-text-muted">Idle %</div>
<div className="text-sm font-medium" style={{ color: idleColor }}>
{idle.idlePct}%
</div>
</div>
</div>
</div>
{/* Model switches */}
{modelSwitches.count > 0 && (
<div className="mb-4">
<div className="mb-2 flex items-center gap-2">
<span className="text-xs font-medium text-text-muted">
Model Switches ({modelSwitches.count})
</span>
{modelSwitches.switchPattern && (
<span
className="rounded px-2 py-0.5 text-xs font-medium"
style={{
backgroundColor: `${assessmentColor(modelSwitches.switchPattern)}20`,
color: assessmentColor(modelSwitches.switchPattern),
}}
>
{assessmentLabel(modelSwitches.switchPattern)}
</span>
)}
</div>
<div className="flex flex-col gap-1">
{modelSwitches.switches.map((sw, idx) => (
<div key={idx} className="flex items-center gap-2 px-2 py-0.5 text-xs">
<span className="text-text-secondary">{sw.from}</span>
<span className="text-text-muted">&rarr;</span>
<span className="text-text">{sw.to}</span>
<span className="ml-auto text-text-muted">msg #{sw.messageIndex}</span>
</div>
))}
</div>
</div>
)}
{/* Key events */}
{keyEvents.length > 0 && (
<div>
<div className="mb-2 text-xs font-medium text-text-muted">Key Events</div>
<div className="flex flex-col gap-1">
{keyEvents.map((event, idx) => (
<div key={idx} className="flex items-center gap-2 px-2 py-0.5 text-xs">
<span className="shrink-0 text-text-muted">
{event.timestamp.toLocaleTimeString()}
</span>
<span className="truncate text-text">{event.label}</span>
{event.deltaHuman && (
<span className="ml-auto shrink-0 text-text-muted">+{event.deltaHuman}</span>
)}
</div>
))}
</div>
</div>
)}
</ReportSection>
);
};