import { sendToAgentChat } from "@agent-native/core/client/agent-chat"; import { useActionQuery } from "@agent-native/core/client/hooks"; import { useT } from "@agent-native/core/client/i18n"; import { IconActivity, IconAlertTriangle, IconArrowUpRight, IconApps, IconChartBar, IconCoin, IconMessages, IconUsersGroup, } from "@tabler/icons-react"; import { useMemo, useState, type ReactNode } from "react"; import { Link, useSearchParams } from "react-router"; import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"; import { DispatchShell } from "../../components/dispatch-shell"; import { Alert, AlertDescription, AlertTitle } from "../../components/ui/alert"; import { Badge } from "../../components/ui/badge"; import { Button } from "../../components/ui/button"; import { ChartContainer, ChartTooltip, ChartTooltipContent, } from "../../components/ui/chart"; import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue, } from "../../components/ui/select"; import { Skeleton } from "../../components/ui/skeleton"; import { UsageAlertsPanel } from "../../components/usage-alerts-panel"; import { cn } from "../../lib/utils"; export function meta() { return [{ title: "Metrics — Dispatch" }]; } interface UsageMetricBucket { key: string; label: string; costCents: number; calls: number; chatCalls: number; inputTokens: number; outputTokens: number; activeUsers: number; lastActiveAt: number | null; } interface UserUsageMetric extends UsageMetricBucket { ownerEmail: string; chatThreads: number; chatMessages: number; lastChatAt: number | null; topApp: string | null; role: string | null; } interface UsageUserOption { email: string; role: string | null; } interface AppAccessMetric { id: string; name: string; path: string; status?: "ready" | "pending"; statusLabel?: string; isDispatch: boolean; accessLabel: string; accessUsers: number; usersWithUsage: number; usageCalls: number; chatCalls: number; costCents: number; lastActiveAt: number | null; } interface DailyUsageMetric { date: string; costCents: number; calls: number; chatCalls: number; activeUsers: number; } interface RecentUsageMetric { id: number; createdAt: number; ownerEmail: string; app: string; label: string; model: string; inputTokens: number; outputTokens: number; costCents: number; prompt: string | null; promptSource: "thread" | "thread-preview" | "not-captured" | "unavailable"; threadId: string | null; runId: string | null; taskId: string | null; sourcePlatform: string | null; sourceId: string | null; } interface UsageBillingMode { unit: "usd" | "builder-credits"; label: string; shortLabel: string; source: "estimated-provider-cost" | "builder-agent-credits"; hardCostMarginMultiplier?: number; creditsPerUsd?: number; } interface DispatchUsageMetrics { billing?: UsageBillingMode; viewScope?: "me" | "workspace"; selectedUserEmail?: string | null; availableUsers?: UsageUserOption[]; sinceDays: number; access: { viewerEmail: string; orgId: string | null; role: string | null; scope: "organization" | "solo"; totalUsers: number; }; totals: { costCents: number; calls: number; chatCalls: number; inputTokens: number; outputTokens: number; cacheReadTokens: number; cacheWriteTokens: number; activeUsers: number; chatThreads: number; chatMessages: number; workspaceApps: number; }; byApp: UsageMetricBucket[]; byUser: UserUsageMetric[]; byLabel: UsageMetricBucket[]; byModel: UsageMetricBucket[]; daily: DailyUsageMetric[]; appAccess: AppAccessMetric[]; recent: RecentUsageMetric[]; } const RANGES = [7, 30, 90] as const; const USD_BILLING: UsageBillingMode = { unit: "usd", label: "Estimated spend", shortLabel: "Cost", source: "estimated-provider-cost", }; function displayAmountFromCostCents( cents: number, billing: UsageBillingMode, ): number { if (billing.unit !== "builder-credits") return cents; const margin = billing.hardCostMarginMultiplier ?? 1.25; const creditsPerUsd = billing.creditsPerUsd ?? 20; const credits = (cents / 100) * margin * creditsPerUsd; return credits <= 0 ? 0 : Math.ceil(credits * 1000) / 1000; } function formatCredits(credits: number): string { if (!Number.isFinite(credits) || credits === 0) return "0 credits"; const maximumFractionDigits = credits < 1 ? 3 : credits < 10 ? 2 : 1; const value = credits.toLocaleString(undefined, { maximumFractionDigits, }); return `${value} ${credits === 1 ? "credit" : "credits"}`; } function formatSpend(cents: number, billing: UsageBillingMode): string { if (billing.unit === "builder-credits") { return formatCredits(displayAmountFromCostCents(cents, billing)); } if (!Number.isFinite(cents) || cents === 0) return "$0.00"; if (Math.abs(cents) < 1) return `${cents.toFixed(3)}¢`; if (Math.abs(cents) < 100) return `${cents.toFixed(2)}¢`; return (cents / 100).toLocaleString(undefined, { style: "currency", currency: "USD", maximumFractionDigits: 2, }); } function formatNumber(value: number): string { return new Intl.NumberFormat(undefined, { notation: value >= 10_000 ? "compact" : "standard", maximumFractionDigits: value >= 10_000 ? 1 : 0, }).format(value); } function formatTokens(value: number): string { return new Intl.NumberFormat(undefined, { notation: "compact", maximumFractionDigits: 1, }).format(value); } function timeAgo(timestamp: number | null): string { if (!timestamp) return "No activity"; const diff = Date.now() - timestamp; if (diff < 60_000) return "just now"; if (diff < 3_600_000) return `${Math.floor(diff / 60_000)}m ago`; if (diff < 86_400_000) return `${Math.floor(diff / 3_600_000)}h ago`; return `${Math.floor(diff / 86_400_000)}d ago`; } function formatTrendDate(value: string): string { const date = new Date(`${value}T12:00:00`); return date.toLocaleDateString(undefined, { month: "short", day: "numeric", }); } function completeTrendRows(rows: DailyUsageMetric[]): DailyUsageMetric[] { if (rows.length < 2) return rows; const byDate = new Map(rows.map((row) => [row.date, row])); const start = new Date(`${rows[0].date}T12:00:00`); const end = new Date(`${rows[rows.length - 1].date}T12:00:00`); const completed: DailyUsageMetric[] = []; for ( const cursor = start; cursor <= end; cursor.setDate(cursor.getDate() + 1) ) { const date = cursor.toISOString().slice(0, 10); completed.push( byDate.get(date) ?? { date, costCents: 0, calls: 0, chatCalls: 0, activeUsers: 0, }, ); } return completed; } function displayApp(value: string | null | undefined): string { const trimmed = value?.trim(); if (!trimmed || trimmed === "unattributed") return "Unattributed"; return trimmed; } function maxSpend( rows: Array<{ costCents: number }>, billing: UsageBillingMode, ): number { return rows.reduce( (max, row) => Math.max(max, displayAmountFromCostCents(row.costCents, billing)), 0, ); } function barWidth(value: number, max: number): string { if (max <= 0 || value <= 0) return "0%"; return `${Math.max(4, Math.round((value / max) * 100))}%`; } function RangeSelector({ value, onChange, }: { value: number; onChange: (value: number) => void; }) { return (
{RANGES.map((range) => ( ))}
); } function MetricCard({ label, value, detail, icon, }: { label: string; value: string; detail: string; icon: ReactNode; }) { return (
{label} {icon}
{value}
{detail}
); } function Panel({ title, icon, children, action, }: { title: string; icon: ReactNode; children: ReactNode; action?: ReactNode; }) { return (
{icon}

{title}

{action}
{children}
); } function LoadingMetrics() { return (
{Array.from({ length: 5 }).map((_, index) => (
))}
); } function ScopeSelector({ value, onChange, }: { value: "me" | "workspace"; onChange: (value: "me" | "workspace") => void; }) { return (
{( [ ["me", "My usage"], ["workspace", "Workspace"], ] as const ).map(([scope, label]) => ( ))}
); } function UserSelector({ value, users, onChange, }: { value: string | null; users: UsageUserOption[]; onChange: (value: string | null) => void; }) { return ( ); } function UsageTrend({ rows, billing, }: { rows: DailyUsageMetric[]; billing: UsageBillingMode; }) { const chartData = completeTrendRows(rows).map((row) => ({ ...row, spend: displayAmountFromCostCents(row.costCents, billing), })); return ( } action={
{billing.shortLabel} Calls
} > {chartData.length === 0 ? (
No usage in this window yet.
) : ( billing.unit === "builder-credits" ? formatCredits(Number(value)) : formatSpend(Number(value), billing) } /> formatTrendDate(String(value))} formatter={(value, name) => [ name === "spend" ? billing.unit === "builder-credits" ? formatCredits(Number(value)) : formatSpend(Number(value), billing) : `${formatNumber(Number(value))} calls`, name === "spend" ? billing.shortLabel : "Calls", ]} /> } /> )}
); } function ReviewUsageButton({ metrics, billing, }: { metrics: DispatchUsageMetrics; billing: UsageBillingMode; }) { function reviewUsage() { const topApps = metrics.byApp .slice(0, 5) .map( (row) => `${displayApp(row.key)}: ${formatSpend(row.costCents, billing)} / ${row.calls} calls`, ) .join("; "); const topLabels = metrics.byLabel .slice(0, 5) .map( (row) => `${row.label}: ${formatSpend(row.costCents, billing)} / ${row.calls} calls`, ) .join("; "); const recentPrompts = metrics.recent .slice(0, 8) .map((row) => { const prompt = row.prompt ? row.prompt.slice(0, 180) : "prompt not captured"; return `${timeAgo(row.createdAt)} | ${displayApp(row.app)} | ${row.label} | ${prompt}`; }) .join("\n"); sendToAgentChat({ message: "Review this LLM usage and explain where the spend is going. Identify repeated, background, or unexpectedly expensive work, cite the strongest evidence, and suggest concrete fixes. Call out missing attribution instead of guessing.", context: [ `Dispatch usage scope: ${metrics.viewScope === "workspace" ? "workspace" : "my account"}.`, `Lookback: ${metrics.sinceDays} days. Viewer: ${metrics.access.viewerEmail}.`, `Total: ${formatSpend(metrics.totals.costCents, billing)}, ${metrics.totals.calls} calls, ${metrics.totals.chatCalls} chat calls, ${formatTokens(metrics.totals.inputTokens + metrics.totals.outputTokens)} input/output tokens.`, `Top apps: ${topApps || "none"}.`, `Top work types: ${topLabels || "none"}.`, `Recent prompt evidence (bounded to the latest 8 rows):\n${recentPrompts || "none"}`, ].join("\n"), submit: true, openSidebar: true, chatTarget: "local", }); } return ( ); } function AppSpendRows({ rows, billing, }: { rows: UsageMetricBucket[]; billing: UsageBillingMode; }) { const max = maxSpend(rows, billing); if (rows.length === 0) { return (
No LLM usage recorded for this window.
); } return (
{rows.map((row) => (
{displayApp(row.key)}
{formatNumber(row.chatCalls)} chats ·{" "} {formatNumber(row.activeUsers)} users
{formatSpend(row.costCents, billing)}
{formatNumber(row.calls)} calls
))}
); } function AppAccessTable({ rows, billing, }: { rows: AppAccessMetric[]; billing: UsageBillingMode; }) { const visibleRows = rows.filter((row) => !row.isDispatch); if (visibleRows.length === 0) { return (
No workspace apps discovered yet.
); } return (
{visibleRows.map((row) => ( ))}
App Access Users Chats {billing.shortLabel} Last activity
{row.name}
{row.path}
{row.status === "pending" ? row.statusLabel || "Builder branch" : row.accessLabel} {formatNumber(row.usersWithUsage)} /{" "} {formatNumber(row.accessUsers)} {formatNumber(row.chatCalls)} {formatSpend(row.costCents, billing)} {timeAgo(row.lastActiveAt)}
); } function UserTable({ rows, billing, }: { rows: UserUsageMetric[]; billing: UsageBillingMode; }) { if (rows.length === 0) { return (
No users have triggered LLM usage in this window.
); } return (
{rows.slice(0, 12).map((row) => ( ))}
User Role Top app Chats Threads Tokens {billing.shortLabel}
{row.ownerEmail}
{timeAgo(row.lastActiveAt ?? row.lastChatAt)}
{row.role ?? "user"} {displayApp(row.topApp)} {formatNumber(row.chatCalls)} {formatNumber(row.chatThreads)} {formatTokens(row.inputTokens + row.outputTokens)} {formatSpend(row.costCents, billing)}
); } function CompactBreakdown({ rows, empty, billing, }: { rows: UsageMetricBucket[]; empty: string; billing: UsageBillingMode; }) { const max = maxSpend(rows, billing); if (rows.length === 0) { return
{empty}
; } return (
{rows.slice(0, 6).map((row) => (
{row.label} {formatSpend(row.costCents, billing)}
))}
); } function RecentTable({ rows, billing, }: { rows: RecentUsageMetric[]; billing: UsageBillingMode; }) { if (rows.length === 0) { return (
No prompts or LLM calls in this window.
); } return (
{rows.slice(0, 10).map((row) => (

{row.prompt || (row.promptSource === "unavailable" ? "Prompt unavailable - linked thread data could not be read." : "Prompt not captured for this call.")}

{timeAgo(row.createdAt)}
{displayApp(row.app)} {row.label} {row.model} {formatSpend(row.costCents, billing)} {row.ownerEmail ? ( <> {row.ownerEmail} ) : null} {row.threadId ? ( Inspect thread ) : null}
))}
); } export default function MetricsRoute() { const t = useT(); const [sinceDays, setSinceDays] = useState(30); const [searchParams, setSearchParams] = useSearchParams(); const scope: "me" | "workspace" = searchParams.get("scope") === "workspace" ? "workspace" : "me"; const userEmail = scope === "workspace" ? searchParams.get("user") || null : null; function setScope(nextScope: "me" | "workspace") { const next = new URLSearchParams(searchParams); if (nextScope === "me") next.delete("scope"); else next.set("scope", nextScope); if (nextScope === "me") next.delete("user"); setSearchParams(next, { replace: true }); } function setUserEmail(nextUserEmail: string | null) { const next = new URLSearchParams(searchParams); if (nextUserEmail) next.set("user", nextUserEmail); else next.delete("user"); setSearchParams(next, { replace: true }); } const { data, isLoading, error } = useActionQuery( "list-dispatch-usage-metrics", { sinceDays, scope, userEmail: userEmail ?? undefined }, ); const metrics = data as DispatchUsageMetrics | undefined; const billing = metrics?.billing ?? USD_BILLING; const totalTokens = useMemo(() => { if (!metrics) return 0; return ( metrics.totals.inputTokens + metrics.totals.outputTokens + metrics.totals.cacheReadTokens + metrics.totals.cacheWriteTokens ); }, [metrics]); return (
{metrics?.selectedUserEmail ? `${metrics.selectedUserEmail}'s usage` : scope === "workspace" ? "Workspace usage" : "Your usage"}
{metrics?.selectedUserEmail ? "Filtered to this workspace member" : scope === "workspace" ? `${metrics?.access.totalUsers ?? 0} users with access` : metrics?.access.viewerEmail || "Signed-in account"}
{scope === "workspace" && metrics ? ( ) : null}
{error ? ( {t("dispatch.pages.metricsUnavailable")} {error instanceof Error ? error.message : t("dispatch.pages.unableToLoadUsage")} ) : null} {isLoading && !metrics ? : null} {metrics ? ( <>
Review this usage with the agent
row.key && row.key !== "unattributed") .map((row) => ({ id: row.key, label: displayApp(row.key) }))} />
} /> } /> } /> } /> } />
} > } action={ Latest {Math.min(metrics.recent.length, 10)} of{" "} {metrics.recent.length} } > }> }>
}> }>
) : null}
); }