"use client";
import { Suspense } from "react";
import { useSearchParams, useRouter } from "next/navigation";
import { useOrchestratorStatus } from "@/hooks/use-agents";
import { useTasks } from "@/hooks/use-tasks";
import {
useUsageSummary,
useUsageTimeSeries,
useAgentUsage,
useTeamUsage,
useModelUsage,
useUsageProjection,
useCacheEfficiency,
useUsageSessions,
} from "@/hooks/use-usage";
import { TaskStatus, Team } from "@/types";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Progress } from "@/components/ui/progress";
import { Skeleton } from "@/components/ui/skeleton";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { OfflineState } from "@/components/ui/offline-state";
import { DeliveryTabContent } from "@/components/metrics/delivery-tab";
import {
UsageTimeSeriesChart,
ModelUsageDonut,
AgentUsageChart,
TeamUsageChart,
SessionsTable,
} from "@/components/metrics";
import {
Activity,
TrendingUp,
TrendingDown,
Clock,
AlertTriangle,
Users,
CheckCircle,
XCircle,
Zap,
Timer,
Coins,
Sparkles,
} from "lucide-react";
import type {
UsageProjection as UP,
CacheEfficiencyResponse as CER,
} from "@/types";
// ─── Humanized number formatting ─────────────────────────────────────────────
/** Format counts with K/M suffix for values >= 1000. */
function humanizeCount(n: number): string {
if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + "M";
if (n >= 1_000) return (n / 1_000).toFixed(1) + "K";
return String(n);
}
/** Format token counts (same as humanizeCount but used for token display). */
function fmtTokens(n: number): string {
if (n >= 1_000_000) return (n / 1_000_000).toFixed(2) + "M";
if (n >= 1_000) return (n / 1_000).toFixed(1) + "K";
return String(n);
}
// ─── Shared sub-components ────────────────────────────────────────────────────
interface MetricCardProps {
title: string;
value: string | number;
subtitle?: string;
icon: React.ReactNode;
trend?: "up" | "down" | "neutral";
trendValue?: string;
}
function MetricCard({
title,
value,
subtitle,
icon,
trend,
trendValue,
}: MetricCardProps) {
const displayValue = typeof value === "number" ? humanizeCount(value) : value;
return (
{title}
{icon}
{displayValue}
{subtitle && (
{subtitle}
)}
{trend && trendValue && (
{trendValue}
)}
);
}
interface TeamHealthCardProps {
team: Team;
activeTasks: number;
blockedTasks: number;
completedToday: number;
}
function TeamHealthCard({
team,
activeTasks,
blockedTasks,
completedToday,
}: TeamHealthCardProps) {
const healthScore = Math.max(0, 100 - blockedTasks * 20);
return (
{team.replace(/_/g, " ")} Cell
);
}
// ─── Performance tab content ─────────────────────────────────────────────────
function PerformanceTabContent() {
const { data: tasks, error: tasksError, refetch: refetchTasks } = useTasks();
const {
data: status,
error: statusError,
refetch: refetchStatus,
} = useOrchestratorStatus();
const isOffline =
(tasksError || statusError) &&
(tasksError?.message?.includes("Network Error") ||
statusError?.message?.includes("Network Error"));
const refetch = () => {
refetchTasks();
refetchStatus();
};
const taskList = tasks || [];
const agentList = status?.agents || [];
// Velocity metrics
const completedToday = taskList.filter((t) => {
if (!t.completed_at) return false;
const completed = new Date(t.completed_at);
const today = new Date();
return completed.toDateString() === today.toDateString();
}).length;
const completedThisWeek = taskList.filter((t) => {
if (!t.completed_at) return false;
const completed = new Date(t.completed_at);
const weekAgo = new Date();
weekAgo.setDate(weekAgo.getDate() - 7);
return completed > weekAgo;
}).length;
// Task status counts
const pending = taskList.filter(
(t) => t.status === TaskStatus.PENDING,
).length;
const inProgress = taskList.filter(
(t) => t.status === TaskStatus.IN_PROGRESS,
).length;
const blocked = taskList.filter(
(t) => t.status === TaskStatus.BLOCKED,
).length;
const awaitingQa = taskList.filter(
(t) => t.status === TaskStatus.AWAITING_QA,
).length;
const completed = taskList.filter(
(t) => t.status === TaskStatus.COMPLETED,
).length;
// Agent counts
const runningAgents =
status?.by_state?.running ||
agentList.filter((a) => a.state === "running").length;
const idleAgents =
status?.by_state?.idle ||
agentList.filter((a) => a.state === "idle" || a.state === "stopped").length;
const waitingAgents =
status?.waiting_count ||
agentList.filter((a) => a.state === "waiting_long").length;
const errorAgents =
status?.by_state?.error ||
agentList.filter((a) => a.state === "error").length;
// Team metrics
const teamMetrics = Object.values(Team).map((team) => {
const teamTasks = taskList.filter((t) => t.team === team);
return {
team,
activeTasks: teamTasks.filter((t) =>
[TaskStatus.IN_PROGRESS, TaskStatus.CLAIMED].includes(t.status),
).length,
blockedTasks: teamTasks.filter((t) => t.status === TaskStatus.BLOCKED)
.length,
completedToday: teamTasks.filter((t) => {
if (!t.completed_at) return false;
const c = new Date(t.completed_at);
const today = new Date();
return c.toDateString() === today.toDateString();
}).length,
};
});
if (isOffline) {
return (
);
}
return (
{/* Velocity Metrics */}
Velocity
}
/>
}
/>
}
/>
0
? Math.round((completed / taskList.length) * 100) + "%"
: "0%"
}
subtitle="Of all tasks"
icon={}
/>
{/* Task Status */}
Task Status
}
/>
}
/>
}
/>
}
/>
}
/>
{/* Agent Status */}
Agent Status
}
/>
}
/>
}
/>
}
/>
{/* Team Health */}
Team Health
{teamMetrics.map((tm) => (
))}
);
}
// ─── Token Usage & Costs tab content ─────────────────────────────────────────
function TokenUsageCostsSection() {
const { data: summary, isLoading: loadingSnap } = useUsageSummary("24h");
const { data: timeSeries, isLoading: loadingTS } = useUsageTimeSeries("24h");
const { data: agentUsage, isLoading: loadingAgents } = useAgentUsage("24h");
const { data: teamUsage, isLoading: loadingTeams } = useTeamUsage("24h");
const { data: sessions, isLoading: loadingSessions } = useUsageSessions(100);
const { data: modelUsage, isLoading: loadingModels } = useModelUsage("24h");
const { data: projection, isLoading: loadingProj } = useUsageProjection();
const { data: cacheStats, isLoading: loadingCache } =
useCacheEfficiency("24h");
const trendUp = (summary?.trend_pct ?? 0) >= 0;
return (
{/* Row 1 — Summary cards */}
}
isLoading={loadingSnap}
/>
}
isLoading={loadingSnap}
/>
}
isLoading={loadingSnap}
/>
) : (
)
}
isLoading={loadingSnap}
/>
}
isLoading={loadingSnap}
/>
}
isLoading={loadingCache}
/>
{/* Row 2 — Time series + model donut */}
{/* Row 3 — Agent bar + team bar */}
{/* Row 4 — Projection + cache efficiency */}
{/* Row 5 — Sessions table */}
);
}
// ─── Helper sub-components ────────────────────────────────────────────────────
interface SummaryCardProps {
title: string;
value: string | undefined;
icon: React.ReactNode;
trend?: { dir: "up" | "down"; label: string };
isLoading: boolean;
}
function SummaryCard({
title,
value,
icon,
trend,
isLoading,
}: SummaryCardProps) {
return (
{title}
{icon}
{isLoading ? (
) : (
<>
{value ?? "—"}
{trend && (
{trend.label}
)}
>
)}
);
}
interface ProjectionCardProps {
projection: UP | undefined;
isLoading: boolean;
}
function ProjectionCard({ projection, isLoading }: ProjectionCardProps) {
return (
Monthly Projection
{isLoading ? (
) : (
{projection != null
? "$" + projection.projected_monthly_cost_usd.toFixed(2)
: "—"}
Based on {projection?.basis_days ?? 7}-day rolling average ($
{projection?.avg_daily_cost_usd.toFixed(4) ?? "—"}/day)
)}
);
}
interface CacheEfficiencyCardProps {
cacheStats: CER | undefined;
isLoading: boolean;
}
function CacheEfficiencyCard({
cacheStats,
isLoading,
}: CacheEfficiencyCardProps) {
const pct = cacheStats ? cacheStats.cache_hit_rate * 100 : 0;
return (
Cache Efficiency
{isLoading ? (
) : (
{pct.toFixed(1)}%
{cacheStats ? fmtTokens(cacheStats.tokens_cache_read) : "—"} cache
reads · saved $
{cacheStats?.cost_saved_by_cache_usd.toFixed(4) ?? "—"}
)}
);
}
// ─── Tab types ────────────────────────────────────────────────────────────────
type MetricsTab = "performance" | "token-usage" | "delivery";
const VALID_METRICS_TABS: MetricsTab[] = [
"performance",
"token-usage",
"delivery",
];
function isValidMetricsTab(value: string | null): value is MetricsTab {
return VALID_METRICS_TABS.includes(value as MetricsTab);
}
// ─── Main page content (uses useSearchParams) ─────────────────────────────────
function MetricsPageContent() {
const router = useRouter();
const searchParams = useSearchParams();
// Read ?tab= from URL, default to "performance"
const rawTab = searchParams.get("tab");
const activeTab: MetricsTab = isValidMetricsTab(rawTab)
? rawTab
: "performance";
function handleTabChange(value: string) {
const params = new URLSearchParams(searchParams.toString());
params.set("tab", value);
router.push(`?${params.toString()}`);
}
return (
{/* Header */}
Metrics
Performance analytics and operational insights
Performance
Token Usage
Delivery
);
}
// Wrap in Suspense for useSearchParams
export default function MetricsPage() {
return (
{Array.from({ length: 4 }).map((_, i) => (
))}
}
>
);
}